Compilation Toolchain

Compilation, Libraries, Debug, Automation

Polytech Lille · Embedded Systems
Jeremie Dequidt

Program Software / App
1/2 devs > 10 devs
1/2 file > 1k files
500 LoCs 1M LoCs
1 arch. n arch.
1 version n versions + m patches
no deps libs + deps
easy to debug really not easy
10s to build 4h to build

Consequences

  • How to work simultaneously on the same code ?
  • How to keep and maintain several version of your code?
  • How to make debugging easier and systematic ?
  • How to automate / optimize compilation and build times

Outline

  1. Compilation (theory)
  2. Compilation (practical)
  3. Libraries
  4. Code Quality / Debug

Compilation (theory)

Expectation Reality
Source file (.c) Source file (.c)
Pre-processor
Abstract Syntax Tree
Intermediate Code
Assembler Code
Binary Code
Link Editor
Executable Executable

Preprocessing

  • Replaces #defines with actual values / instructions
  • Copy included files
 gcc -E my_prog.c

Preprocessing - Example

util.h


int compare(int a, int b)
{
    if (a == b) return 0;
    if (a < b)  return 1;
    return -1;
}
    

Preprocessing - Example

my_prog.c


#include "util.h"

#define N 20
#define M 35

int main()
{
    int ret_value = compare(N, M);
    return ret_value;
}
    

Preprocessing - Example

my_prog.e

# 1 "" 2
# 1 "my_prog.c" 2
# 1 "./util.h" 1
int compare(int a, int b)
{
    if (a == b) return 0;
    if (a < b) return 1;
    return -1;
}
# 2 "my_prog.c" 2
int main()
{
    int ret_value = compare(20, 35);
    return ret_value;
}
    

Abstract Syntax Tree (AST)

  • language independent
  • type verification
  • allows some optimizations

gcc -Xclang -ast-dump -fsyntax-only my_prog.c
    

AST - Example


[...]
|-FunctionDecl 0x7fd2be01a970 <./util.h:1:1, line:6:1> line:1:5 used compare 'int (int, int)'
| |-ParmVarDecl 0x7fd2be01a818  col:17 used a 'int'
| |-ParmVarDecl 0x7fd2be01a898  col:24 used b 'int'
| `-CompoundStmt 0x7fd2be01ac60 
|   |-IfStmt 0x7fd2be01ab28 
|   | |-BinaryOperator 0x7fd2be01aad8  'int' '=='
|   | | |-ImplicitCastExpr 0x7fd2be01aaa8  'int' 
|   | | | `-DeclRefExpr 0x7fd2be01aa68  'int' lvalue ParmVar 0x7fd2be01a818 'a' 'int'
|   | | `-ImplicitCastExpr 0x7fd2be01aac0  'int' 
|   | |   `-DeclRefExpr 0x7fd2be01aa88  'int' lvalue ParmVar 0x7fd2be01a898 'b' 'int'
|   | `-ReturnStmt 0x7fd2be01ab18 
|   |   `-IntegerLiteral 0x7fd2be01aaf8  'int' 0
[...]
    

Intermediate Code

  • Transform AST into ASM for ideal computer (unlimited registers, memory, no latency...)
  • ASM code very simple and easy to read

            gcc -S -emit-llvm my_prog.c
    

Intermediate Code - Example


; ModuleID = 'my_prog.c'
source_filename = "my_prog.c"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx11.0.0"
; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @compare(i32 %0, i32 %1) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i32, align 4
  store i32 %0, i32* %4, align 4
  store i32 %1, i32* %5, align 4
  %6 = load i32, i32* %4, align 4
  %7 = load i32, i32* %5, align 4
  %8 = icmp eq i32 %6, %7
  br i1 %8, label %9, label %10

9:                                                ; preds = %2
  store i32 0, i32* %3, align 4
  br label %16

10:                                               ; preds = %2
  %11 = load i32, i32* %4, align 4
  %12 = load i32, i32* %5, align 4
  %13 = icmp slt i32 %11, %12
  br i1 %13, label %14, label %15

14:                                               ; preds = %10
  store i32 1, i32* %3, align 4
  br label %16

15:                                               ; preds = %10
  store i32 -1, i32* %3, align 4
  br label %16

16:                                               ; preds = %15, %14, %9
  %17 = load i32, i32* %3, align 4
  ret i32 %17
}
; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  %3 = call i32 @compare(i32 20, i32 35)
  store i32 %3, i32* %2, align 4
  %4 = load i32, i32* %2, align 4
  ret i32 %4
}

attributes #0 = { noinline nounwind optnone ssp uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "darwin-stkchk-strong-link" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "probe-stack"="___chkstk_darwin" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}

!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 11, i32 1]}
!1 = !{i32 1, !"wchar_size", i32 4}
!2 = !{i32 7, !"PIC Level", i32 2}
!3 = !{!"Apple clang version 12.0.0 (clang-1200.0.32.29)"}
    

Assembler Code

  • Transform IR into target architecture instructions
  • Require excellent knowledge of hardware

gcc -S my_prog.c
    

Assembler Code - Example

my_prog.S


    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 11, 0 sdk_version 11, 1
    .globl  _compare   ## -- Begin function compare
    .p2align    4, 0x90
_compare:              ## @compare
    .cfi_startproc
## %bb.0:
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    movl    %edi, -8(%rbp)
    movl    %esi, -12(%rbp)
    movl    -8(%rbp), %eax
    cmpl    -12(%rbp), %eax
    jne LBB0_2
## %bb.1:
    movl    $0, -4(%rbp)
    jmp LBB0_5
LBB0_2:
    movl    -8(%rbp), %eax
    cmpl    -12(%rbp), %eax
    jge LBB0_4
## %bb.3:
    movl    $1, -4(%rbp)
    jmp LBB0_5
LBB0_4:
    movl    $-1, -4(%rbp)
LBB0_5:
    movl    -4(%rbp), %eax
    popq    %rbp
    retq
    .cfi_endproc
                   ## -- End function
    .globl  _main      ## -- Begin function main
    .p2align    4, 0x90
_main:             ## @main
    .cfi_startproc
## %bb.0:
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    subq    $16, %rsp
    movl    $0, -4(%rbp)
    movl    $20, %edi
    movl    $35, %esi
    callq   _compare
    movl    %eax, -8(%rbp)
    movl    -8(%rbp), %eax
    addq    $16, %rsp
    popq    %rbp
    retq
    .cfi_endproc
                   ## -- End function
.subsections_via_symbols
        

Binary Code

  • Transform ASM into binary object

            as my_prog.s
    

Binary Code - Example

my_prog.o


0000000 cf fa ed fe 07 00 00 01 03 00 00 00 01 00 00 00
0000010 04 00 00 00 b8 01 00 00 00 20 00 00 00 00 00 00
0000020 19 00 00 00 38 01 00 00 00 00 00 00 00 00 00 00
0000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000040 28 01 00 00 00 00 00 00 d8 01 00 00 00 00 00 00
0000050 28 01 00 00 00 00 00 00 07 00 00 00 07 00 00 00
0000060 03 00 00 00 00 00 00 00 5f 5f 74 65 78 74 00 00
0000070 00 00 00 00 00 00 00 00 5f 5f 54 45 58 54 00 00
0000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000090 7a 00 00 00 00 00 00 00 d8 01 00 00 04 00 00 00
00000a0 00 03 00 00 01 00 00 00 00 04 00 80 00 00 00 00
00000b0 00 00 00 00 00 00 00 00 5f 5f 63 6f 6d 70 61 63
00000c0 74 5f 75 6e 77 69 6e 64 5f 5f 4c 44 00 00 00 00
00000d0 00 00 00 00 00 00 00 00 80 00 00 00 00 00 00 00
00000e0 40 00 00 00 00 00 00 00 58 02 00 00 03 00 00 00
00000f0 08 03 00 00 02 00 00 00 00 00 00 02 00 00 00 00
0000100 00 00 00 00 00 00 00 00 5f 5f 65 68 5f 66 72 61
0000110 6d 65 00 00 00 00 00 00 5f 5f 54 45 58 54 00 00
0000120 00 00 00 00 00 00 00 00 c0 00 00 00 00 00 00 00
0000130 68 00 00 00 00 00 00 00 98 02 00 00 03 00 00 00
0000140 00 00 00 00 00 00 00 00 0b 00 00 68 00 00 00 00
0000150 00 00 00 00 00 00 00 00 32 00 00 00 18 00 00 00
0000160 01 00 00 00 00 00 0b 00 00 01 0b 00 00 00 00 00
0000170 02 00 00 00 18 00 00 00 18 03 00 00 02 00 00 00
0000180 38 03 00 00 10 00 00 00 0b 00 00 00 50 00 00 00
0000190 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00
00001a0 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
00001d0 00 00 00 00 00 00 00 00 55 48 89 e5 89 7d f8 89
00001e0 75 f4 8b 45 f8 3b 45 f4 0f 85 0c 00 00 00 c7 45
00001f0 fc 00 00 00 00 e9 1f 00 00 00 8b 45 f8 3b 45 f4
0000200 0f 8d 0c 00 00 00 c7 45 fc 01 00 00 00 e9 07 00
0000210 00 00 c7 45 fc ff ff ff ff 8b 45 fc 5d c3 66 2e
0000220 0f 1f 84 00 00 00 00 00 55 48 89 e5 48 83 ec 10
0000230 c7 45 fc 00 00 00 00 bf 14 00 00 00 be 23 00 00
0000240 00 e8 00 00 00 00 89 45 f8 8b 45 f8 48 83 c4 10
0000250 5d c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000260 46 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00
0000270 00 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00
0000280 2a 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00
0000290 00 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00
00002a0 01 7a 52 00 01 78 10 01 10 0c 07 08 90 01 00 00
00002b0 24 00 00 00 1c 00 00 00 20 ff ff ff ff ff ff ff
00002c0 46 00 00 00 00 00 00 00 00 41 0e 10 86 02 43 0d
00002d0 06 00 00 00 00 00 00 00 24 00 00 00 44 00 00 00
00002e0 48 ff ff ff ff ff ff ff 2a 00 00 00 00 00 00 00
00002f0 00 41 0e 10 86 02 43 0d 06 00 00 00 00 00 00 00
0000300 6a 00 00 00 00 00 00 2d 20 00 00 00 01 00 00 06
0000310 00 00 00 00 01 00 00 06 07 00 00 00 0f 01 00 00
0000320 00 00 00 00 00 00 00 00 01 00 00 00 0f 01 00 00
0000330 50 00 00 00 00 00 00 00 00 5f 6d 61 69 6e 00 5f
0000340 63 6f 6d 70 61 72 65 00
                            
                        

Binary - Another Example

str_ex.c


int main()
{
    const char line[] = "Hello SE3 !";

    return 0;
}
    

Binary - Another Example

str_ex.o


[...]
0000270 31 c0 48 83 c4 20 5d c3 e8 00 00 00 00 0f 0b 48
0000280 65 6c 6c 6f 20 53 45 33 20 21 00 00 00 00 00 00
[...]
    

echo "Hello SE3 \!"  | hexdump
    

0000000 48 65 6c 6c 6f 20 53 45 33 20 21 0a
    

Link Editor

  • Step required if you have multiple object files...
  • ... or if you use libraries

Link Editor - Example

ld_util.c


int compare(int a, int b)
{
    if (a == b) return 0;
    if (a < b)  return 1;
    return -1;
}
    

ld_main.c


#define N 20
#define M 35
int compare(int, int);

int main()
{
    int ret_value = compare(N, M);
    return ret_value;
}
    

Link Editor - Example


$ gcc ld_main.c
Undefined symbols for architecture x86_64:
  "_compare", referenced from:
      _main in ld_main-f1b913.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1
                        (use -v to see invocation)
    

Link Editor - Example


$ gcc -c ld_main.c
$ gcc -c ld_util.c
$ nm ld_main.o
                 U _compare
0000000000000000 T _main
$ nm ld_util.o
0000000000000000 T _compare
$ gcc ld_main.o ld_util.o
$ nm a.out
0000000100000000 T __mh_execute_header
0000000100003f70 T _compare
0000000100003f40 T _main
                 U dyld_stub_binder
    

Take Away Messages

  • Program compilation is a complex process with lots of steps
  • implicit declaration of function or use of undeclared identifier errors are related to the preprocessor -> check your includes or definitions
  • Undefined symbols or Undefined references errors are related to the linker ⟹ check your compilation commands and add needed libraries or object files

Compilation (practical)

Objectives:

if a task is repetitive, automate it !
  • Compilation is very repetitive
  • Typing compilation commands with large projects
    • is very tedious
    • is non optimal

Modular Programming

Build easy to read / easy to maintain code

  • by splitting features in files...
  • located in folders or modules that share the same functionalities (like I/O, Rendering, Network, Users...)

This allows to control dependencies between module, allows to build libraries...

Example Quake3 Arena

Example Quake3 Arena (1999)


$ cloc .
     600 text files.
     591 unique files.
      53 files ignored.
-------------------------------------------
Language      files  blank  comment    code
-------------------------------------------
C               328  37249    58865  192435
C/C++ Header    143   6285     7688   23982
C++               8    725      724    2438
make              3    214      230    1792
Assembly          9    267      406    1224
Bourne Shell      8     61       48     305
[...]
-------------------------------------------
SUM:            547  47047    71726  248147
-------------------------------------------
    

Example Quake3 Arena (1999)


$ tree -d
.
├── botlib
├── bspc
├── cgame
├── client
├── game
├── jpeg-6
├── macosx
├── null
├── q3_ui
├── qcommon
├── renderer
├── server
├── splines
├── ui
├── unix
└── win32
    

Modular Prog. - How To

  • Place declarations of reusable resources (functions, variables, structures) in header files (.h)
  • Place definitions of reusable resources in source files and place implementation details in source files (.c)

Modular Prog. - Exemple

inout.h


#define TAILLE_MAX      256
#define INTENSITE_MAX   255
#define LARGEUR         800
#define HAUTEUR         600

int chargeImage(const char *nom_fichier,
    unsigned char tableau[HAUTEUR][LARGEUR]);

int sauvegardeImage(const char *nom_fichier,
    unsigned char tableau[HAUTEUR][LARGEUR]);
    

Modular Prog. - Exemple

inout.c


/*
 *  Projet de Programmation Structurée
 *  Département IMA - 3ieme Année
 *  Polytech'Lille - 2010/2011
 *  By Jérémie Dequidt
 */

#include "inout.h"
#define DEBUG true

/*********************************************************
 * Base functions : input/output of pictures
 *********************************************************
 */


/*
 * Load a PGM picture into a char vect
 * - Parameters :
 *      nom_fichier: The input file
 *      vecteur: char pointer where the pixels are stored
 * - Return value : 1 if everything went well, 0 otherwise
 */
int chargeImage(const char *nom_fichier,
    unsigned char vecteur[HAUTEUR][LARGEUR])
{
  /* Variable Declaration */
  FILE *fp;
  unsigned int type;
  int imax, l, h, debug;
  unsigned char buffer[TAILLE_MAX];

  // [...]
}

/*
  \brief Fonction to save a picture
  \param nom_fichier output file where we want to save
  \param type output type (grey / color)
  \param tableau char array (= where to find pixels)
  \return 1 if everything went well, 0 otherwise
*/
int sauvegardeImage(const char *nom_fichier,
    unsigned char vecteur[HAUTEUR][LARGEUR])
{
  FILE *fp;

  /* File opening */
  [...]
}
  

Modular Prog. - Example

main.c


#include 
#include 
#include "inout.h"

//------------FONCTIONS--------------------

//image en négatif
void negatif(unsigned char imagea[HAUTEUR][LARGEUR])  {
  for(int i=0; i< HAUTEUR; i++)   {
    for (int j = 0; j < LARGEUR; j++) {
      imagea[i][j] = 255 - imagea[i][j]; // assign inverse of pixel intensity
    }
  }
}
//[...]


int main()
{
  unsigned char image[HAUTEUR][LARGEUR], image2[HAUTEUR][LARGEUR];
  int charge = chargeImage("../images/royalPalaceMadrid.pgm", image);
  int charge2 = chargeImage("../images/fog-1535201_800.pgm", image2);
  // [...]
  return 0;
}

    

Error: multiple inclusion

Remember pre-processing step in compilation

// a.h
const int g_variable = 50;

// b.h
#include "a.h"

int func()
{
    return g_variable;
}

// main.c
#include "a.h"
#include "b.h"

int main()
{
    int var = g_variable;
    return var + func();
}
    

Error: multiple inclusion

Remember pre-processing step in compilation

$ gcc main.c
In file included from main.c:2:
In file included from ./b.h:1:
./a.h:1:11: error: redefinition of 'g_variable'
const int g_variable = 50;
          ^
main.c:1:10: note: './a.h' included multiple times,
additional include site here
#include "a.h"
         ^
        

Error: multiple inclusion

main.e


# 1 "main.c"
# 1 "./a.h" 1
const int g_variable = 50;
# 2 "main.c" 2
# 1 "./b.h" 1
# 1 "./a.h" 1
const int g_variable = 50;
# 2 "./b.h" 2

int func()
{
    return g_variable;
}
# 3 "main.c" 2

int main()
{
    int var = g_variable;
    return var + func();
}
    

Error: cyclic inclusion


// c.h
#include "d.h"
int g_var = 10;

int temp_c()
{
    return temp_d() + g_var;
}
// d.h
#include "c.h"

int temp_d()
{
    return temp_c() + g_var;
}
// main.c
#include "c.h"

int main()
{
    return temp_c();
}
    

Error: cyclic inclusion


# 1 "maine.c"
# 1 "" 1
# 1 "" 3
# 366 "" 3
# 1 "" 1
# 1 "" 2
# 1 "maine.c" 2
# 1 "./c.h" 1
# 1 "./d.h" 1
# [... repeated 200x]
int g_var = 10;

int temp_c()
{
    return temp_d() + g_var;
}
# 2 "./d.h" 2

int temp_d()
{
    return temp_c() + g_var;
}
# 2 "./c.h" 2

int g_var = 10;

int temp_c()
{
    return temp_d() + g_var;
}
# 2 "./d.h" 2
// [...]

# 2 "maine.c" 2

int main()
{
    return temp_c();
}
    

Solution: header guard


#ifndef __HEADER_NAME__
#define __HEADER_NAME__

// file content

#endif
    

Automatic compilation : Makefile

From Q3 example

  • with 328 .c files
  • at least 328 gcc -c ...
  • ... and 1 gcc -o commands are required
  • to build the executable
  • (hint) it is way more than that 😱

Anatomy of make

  • requires a file Makefile
  • the Makefile contains comments ...
  • ... variables, rules to build the program

Makefile: example


# Makefile for project S5
TARGET= project
CFLAGS=-g -W -Wall -Wextra
LDFLAGS=-lm

default: $(TARGET)

inout.o: inout.c inout.h
    gcc $(CFLAGS) -c inout.c

main.o: main.c inout.h
    gcc $(CFLAGS) -c main.c

$(TARGET): main.o inout.o
    gcc $(LDFLAGS) main.o inout.o -o $(TARGET)

.PHONY: clean
clean:
    rm -f *.o
    rm -f $(TARGET)
    

Anatomy of rules


target: list of dependencies
[TAB]command(s) to build the target
    

Makefile usage


make
    
is equivalent to

make -f Makefile default
    

assumes that the default target exists

Limitations

  • Lots of copy-pastes

main.o: main.c inout.h
    gcc $(CFLAGS) -c main.c

$(TARGET): main.o inout.o
    gcc $(LDFLAGS) main.o inout.o -o $(TARGET)
    

Internal variables

$@ the target
$* the target without extension
$< the first dependency
$^ all the dependencies

Makefile with int. vars


inout.o: inout.c inout.h
    gcc $(CFLAGS) -c $<

main.o: main.c inout.h
    gcc $(CFLAGS) -c $<

$(TARGET): main.o inout.o
    gcc $(LDFLAGS) $^ -o $@
    

Limitations

  • Similar rules

inout.o: inout.c inout.h
    gcc $(CFLAGS) -c $<

main.o: main.c inout.h
    gcc $(CFLAGS) -c $<
        

Inference Rules

  • = avoid similar rules

.c.o:
    gcc $(CFLAGS) -c $<
    

defines how c files are built in object files

Makefile with inf. rules


TARGET= project
CFLAGS=-g -W -Wall -Wextra
LDFLAGS=-lm

default: $(TARGET)

.c.o:
    gcc $(CFLAGS) -c $<

$(TARGET): main.o inout.o
    gcc $(LDFLAGS) $^ -o $@

.PHONY: clean
clean:
    rm -f *.o
    rm -f $(TARGET)
    

Comments about inf. rules


.c.o:
    gcc $(CFLAGS) -c $<
    

dependency of headers is lost 🙁

Automatic dependency generation.


$ gcc -MMD -c ld_main.c
$ cat ld_main.d
ld_main.o: ld_main.c
        

for each source file (.c), a corresponding .d file is generated that contains dependencies

Makefile with deps.


TARGET= project
CFLAGS=-g -W -Wall -Wextra -MMD
LDFLAGS=-lm
DEPS=main.d inout.d

default: $(TARGET)

.c.o:
    gcc $(CFLAGS) -c $<

$(TARGET): main.o inout.o
    gcc $(LDFLAGS) $^ -o $@

-include $(DEPS)

.PHONY: clean
clean:
    rm -f *.o
    rm -f $(TARGET)
    

Generic Makefile

Automatic list of files


SRC=$(wildcard *.c)
    

every source file (.c) in the current folder


DEPS=$(SRC:.c=.d)
    

generates a list of filenames where .c extension is replaced by .d extension

Generic Makefile

                            
TARGET= project
CFLAGS=-g -W -Wall -Wextra -MMD
LDFLAGS=-lm
SRC=$(wildcard *.c)
DEPS=$(SRC:.c=.d)
OBJ=$(SRC:.c=.o)

default: $(TARGET)

.c.o:
    gcc $(CFLAGS) -c $<

$(TARGET): $(OBJ)
    gcc $(LDFLAGS) $^ -o $@

-include $(DEPS)

.PHONY: clean
clean:
    rm -f *.o
    rm -f $(TARGET)
                            
                        

Adding options


DEBUG=yes #no
ifeq ($(DEBUG),yes)
    CFLAGS=-W -Wall -ansi -g -DDEBUG
else
    CFLAGS=-O3
endif
    

Libraries

Principle

  • Reusable package (without providing source)
  • Contains binary file and associated header files

Examples

  • libc: (printf / scanf / malloc...)
  • libm: (sqrt / pow / floor /...

these libraries are now linked automatically in any C program

How To... Use

  • libraries follow the pattern libNAME.yy
  • to link a library, you need to add -lNAME for gcc
  • Default search paths are /usr/lib/ and /usr/local/lib/, if your libraries are located elsewhere you need to specifiy the location with -Lpath

Static vs Dynamic

Libraries can be static or dynamic

  • Static (.lib for Windows, .a for Unix/Darwin)
  • Dynamic (.dll for Windows, .so for Unix, .dylib for Darwin)

Static

Embedded in the program

  • Pros: no dependencies
  • Cons: executable size, if the library is modified the program needs to be rebuilt

Dynamic

linking done at execution

  • Pros: lighter program, no rebuild if library is modified, library is present at most 1 times in RAM
  • Cons: starting delay, dependencies

Dependencies

How to be sure that a program with dynamic libs can run on other computers ?

  • A program will not run if a library is not available or older than required

Versions

A dynamic lib follows the pattern: libNAME.M.m.p.EXT

  • M= major version (backward compatibility not guaranteed
  • m= minor (backward compatibility, bugs removed)
  • p= patch (bugs removed, no incompatibility)

Know your libs


$ ls -l /usr/local/lib/libgpg*
lrwxr-xr-x 52 jeremie 17 Nov  2020 /usr/local/lib/libgpg-error.0.dylib -> ../Cellar/libgpg-error/1.39/lib/libgpg-error.0.dylib
lrwxr-xr-x 46 jeremie 17 Nov  2020 /usr/local/lib/libgpg-error.a -> ../Cellar/libgpg-error/1.39/lib/libgpg-error.a
lrwxr-xr-x 50 jeremie 17 Nov  2020 /usr/local/lib/libgpg-error.dylib -> ../Cellar/libgpg-error/1.39/lib/libgpg-error.dylib
lrwxr-xr-x 44 jeremie 17 Nov  2020 /usr/local/lib/libgpgme.11.dylib -> ../Cellar/gpgme/1.15.0/lib/libgpgme.11.dylib
lrwxr-xr-x 37 jeremie 17 Nov  2020 /usr/local/lib/libgpgme.a -> ../Cellar/gpgme/1.15.0/lib/libgpgme.a
lrwxr-xr-x 41 jeremie 17 Nov  2020 /usr/local/lib/libgpgme.dylib -> ../Cellar/gpgme/1.15.0/lib/libgpgme.dylib
lrwxr-xr-x 45 jeremie 17 Nov  2020 /usr/local/lib/libgpgmepp.6.dylib -> ../Cellar/gpgme/1.15.0/lib/libgpgmepp.6.dylib
lrwxr-xr-x 39 jeremie 17 Nov  2020 /usr/local/lib/libgpgmepp.a -> ../Cellar/gpgme/1.15.0/lib/libgpgmepp.a
lrwxr-xr-x 43 jeremie 17 Nov  2020 /usr/local/lib/libgpgmepp.dylib -> ../Cellar/gpgme/1.15.0/lib/libgpgmepp.dylib
    

Know your libs

When you link a library...


gcc test.c -lgpgme
    

by default the one with the biggest M,m,p is chosen

Required dynamic libs


$ ldd /usr/bin/gcc
    linux-vdso.so.1 (0x00007fff967eb000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fce65c6d000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fce65aac000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fce65e00000)
    

Required dynamic libs


#include 
#include 

int main(int argc, char** argv)
{
   char * buffer = malloc(1024);
   sprintf(buffer, "%d", argc);
   printf("%s",buffer);
}
    

Required dynamic libs


$ gcc test.c
$ ldd a.out
    linux-vdso.so.1 (0x00007ffd099a3000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7926644000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f792681a000)
    

Static lib build

to create libZZZZ.a


$ ar rcs libZZZZ.a xxxx.o yyyy.o
$ ar -t libXXXX.a
xxxx.o
yyyy.o
    

Dynamic lib build

to create libtest.so


$ gcc [options] -fPIC -c xxxx.c yyyy.c
$ gcc -shared -Wl,-soname,libtest.so.1 -o libtest.so.1.0   *.o
    

Writing better programs

Write Readable Programs

  • use formatting conventions / best practices
  • use clang-format for automatic formatting

clang-format -I fichier.c
    

Write Readable Programs

  • use formatting conventions / best practices
  • use clang-format for automatic formatting

clang-format can be included in most code editors (vim, Sublime Text, Visual Studio...)

Write Readable Programs


#include 
#include 

#include "inout.h"

// selection par Rectangle

void selectionR(unsigned char m[HAUTEUR][LARGEUR],int x,int y,int x1,int y1, unsigned char  reception[HAUTEUR][LARGEUR]){

  int j,i;

  for(i=x;i< x1;i++)
    for(j=y;j< y1;++j){
      reception[i][j]=m[i][j];
       m[i][j]=0;
    }
}
    

Write Readable Programs


#include 
#include 

#include "inout.h"

// selection par Rectangle

void selectionR (unsigned char m[HAUTEUR][LARGEUR],
                 int           x,
                 int           y,
                 int           x1,
                 int           y1,
                 unsigned char reception[HAUTEUR][LARGEUR])
{

    int j, i;

    for (i = x; i < x1; i++)
        for (j = y; j < y1; ++j)
        {
            reception[i][j] = m[i][j];
            m[i][j]         = 0;
        }
}
    

Write Readable Programs

Principles

  • KISS: Keep It Stupid Simple
  • DRY: Don't Repeat Yourself

Write Readable Programs

Applying DRY with pmd (duplicates search)


$ pmd cpd --minimum-tokens 70 --language c --files projet.c
Found a 4 line (112 tokens) duplication in the following files:
Starting at line 43 of projet.c
Starting at line 79 of projet.c

void selection_ellipse(unsigned char image[HAUTEUR][LARGEUR],int C_x, int C_y, int a, int b, unsigned char selection[2*b][2*a]){
    for (int y = 0; y <2*b; y++) {  //balaie un rectangle de hauteur 2b et de largeur 2a (rectangle contenant l'ellipse)
        for (int x = 0; x < 2*a; x++) {
            if   ( (  (pow((x-a),2))/(a*a) + (pow((y-b),2))/(b*b))  <= 1 ) //vérifie que la portion d'image est bien dans l'ellipse

    

Enable Warnings


#include 

int main ()
{
    int *p;
    printf ("Val = %d\n", *p);

    return 0;
}
    

Enable Warnings


$ gcc test_w.c
$ gcc test_w.c -W -Wall -Wextra
test_w.c:6:27: warning: variable 'p' is uninitialized when used here [-Wuninitialized]
    printf("Val = %d\n", *p);
                          ^
test_w.c:5:11: note: initialize the variable 'p' to silence this warning
    int *p;
          ^
           = NULL
1 warning generated.
    

Use (static) checkers

allows to detect common mistakes
  • clang-tidy (CERT guidelines)
  • cppcheck with MISRA, CERT add-ons (embedded programming guidelines)
  • rats
  • oclint

Use (static) checkers

from one student project (SE3 2020-2021)


$ gcc negative.c
    

= no errors, no warnings

Use (static) checkers

from one student project (SE3 2020-2021)


$ gcc negative.c  -c -W -Wall -Wextra
negative.c:212:8: warning: unused variable 'imageMixe' [-Wunused-variable]
          int imageMixe=sauvegardeImage("Image_Resultat.pgm",re);
              ^
negative.c:221:7: warning: unused variable 'imageMixe' [-Wunused-variable]
         int imageMixe=sauvegardeImage("Image_Resultat.pgm",re);
             ^
negative.c:230:7: warning: unused variable 'imageMixe' [-Wunused-variable]
         int imageMixe=sauvegardeImage("Image_Resultat.pgm",re);
             ^
negative.c:239:13: warning: unused variable 'imageMixe' [-Wunused-variable]
               int imageMixe=sauvegardeImage("Image_Resultat.pgm",re);
                   ^
negative.c:91:73: warning: unused variable 'charge6' [-Wunused-variable]
      int charge5 = chargeImage("../images/fog-1535201_800.pgm",image5),charge6=chargeImage("../images/royalPalaceMadrid.pgm",image6);
                                                                        ^
negative.c:90:139: warning: unused variable 'magic1' [-Wunused-variable]
      unsigned char image5[HAUTEUR][LARGEUR], image6[HAUTEUR][LARGEUR],copieMagic[HAUTEUR][LARGEUR]={0},copieMagic2[HAUTEUR][LARGEUR]={0},magic1[HAUTEUR][LARGEUR]={0},magic2[HAUTEUR]...
                                                                                                                                          ^
6 warnings generated.
    

= 6 warnings

Use (static) checkers

from one student project (SE3 2020-2021)


$ clang-tidy --quiet -checks='*' negative.c --
negative.c:12:3: warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]
negative.c:12:7: warning: variable 'j' is not initialized [cppcoreguidelines-init-variables]
negative.c:12:9: warning: variable 'i' is not initialized [cppcoreguidelines-init-variables]
negative.c:14:20: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:25:3: warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]
negative.c:25:7: warning: variable 'i' is not initialized [cppcoreguidelines-init-variables]
negative.c:25:9: warning: variable 'j' is not initialized [cppcoreguidelines-init-variables]
negative.c:28:9: warning: narrowing conversion from 'float' to 'int' [bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions]
negative.c:28:14: warning: narrowing conversion from 'int' to 'float' [bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions]
negative.c:28:25: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:29:11: warning: narrowing conversion from 'float' to 'int' [bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions]
negative.c:29:16: warning: narrowing conversion from 'int' to 'float' [bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions]
negative.c:29:27: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:30:12: warning: narrowing conversion from 'int' to 'float' [bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions]
negative.c:30:19: warning: narrowing conversion from 'int' to 'float' [bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions]
negative.c:30:34: warning: narrowing conversion from 'int' to 'float' [bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions]
negative.c:30:41: warning: narrowing conversion from 'int' to 'float' [bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions]
negative.c:39:6: warning: function 'magicwand' is within a recursive call chain [misc-no-recursion]
negative.c:39:6: note: example recursive call chain, starting from function 'magicwand'
negative.c:47:6: note: Frame #1: function 'magicwand' calls function 'magicwand' here:
negative.c:47:6: note: ... which was the starting point of the recursive call chain; there may be other cycles
negative.c:41:26: warning: 800 is a magic number; consider replacing it with a named constant [cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers]
negative.c:41:36: warning: 600 is a magic number; consider replacing it with a named constant [cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers]
negative.c:42:12: warning: narrowing conversion from 'int' to 'float' [bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions]
negative.c:42:29: warning: narrowing conversion from 'int' to 'float' [bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions]
negative.c:44:14: warning: 255 is a magic number; consider replacing it with a named constant [cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers]
negative.c:78:5: warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]
negative.c:79:5: warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]
negative.c:80:5: warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]
negative.c:80:9: warning: variable 'x1' is not initialized [cppcoreguidelines-init-variables]
negative.c:80:12: warning: variable 'x2' is not initialized [cppcoreguidelines-init-variables]
negative.c:80:15: warning: variable 'y1' is not initialized [cppcoreguidelines-init-variables]
negative.c:80:18: warning: variable 'y2' is not initialized [cppcoreguidelines-init-variables]
negative.c:84:6: warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]
negative.c:85:6: warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]
negative.c:86:6: warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]
negative.c:86:12: warning: variable 'a' is not initialized [cppcoreguidelines-init-variables]
negative.c:86:14: warning: variable 'b' is not initialized [cppcoreguidelines-init-variables]
negative.c:86:17: warning: variable 'xc' is not initialized [cppcoreguidelines-init-variables]
negative.c:86:20: warning: variable 'yc' is not initialized [cppcoreguidelines-init-variables]
negative.c:89:7: warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]
negative.c:89:11: warning: variable 'x' is not initialized [cppcoreguidelines-init-variables]
negative.c:89:13: warning: variable 'y' is not initialized [cppcoreguidelines-init-variables]
negative.c:90:7: warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]
negative.c:91:7: warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]
negative.c:95:7: warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]
negative.c:95:20: warning: variable 'jeu' is not initialized [cppcoreguidelines-init-variables]
negative.c:97:7: warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]
negative.c:97:13: warning: variable 't' is not initialized [cppcoreguidelines-init-variables]
negative.c:97:16: warning: variable 'ih' is not initialized [cppcoreguidelines-init-variables]
negative.c:97:20: warning: variable 'ib' is not initialized [cppcoreguidelines-init-variables]
negative.c:98:7: warning: multiple declarations in a single statement reduces readability [readability-isolate-declaration]
negative.c:106:8: warning: 'scanf' used to convert a string to an integer value, but function will not report conversion errors; consider using 'strtol' instead [cert-err34-c]
negative.c:114:4: warning: 'scanf' used to convert a string to an integer value, but function will not report conversion errors; consider using 'strtol' instead [cert-err34-c]
negative.c:117:4: warning: 'scanf' used to convert a string to an integer value, but function will not report conversion errors; consider using 'strtol' instead [cert-err34-c]
negative.c:120:4: warning: 'scanf' used to convert a string to an integer value, but function will not report conversion errors; consider using 'strtol' instead [cert-err34-c]
negative.c:123:4: warning: 'scanf' used to convert a string to an integer value, but function will not report conversion errors; consider using 'strtol' instead [cert-err34-c]
negative.c:128:30: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:129:32: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:134:17: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:146:6: warning: 'scanf' used to convert a string to a floating-point value, but function will not report conversion errors; consider using 'strtof' instead [cert-err34-c]
negative.c:149:6: warning: 'scanf' used to convert a string to a floating-point value, but function will not report conversion errors; consider using 'strtof' instead [cert-err34-c]
negative.c:152:6: warning: 'scanf' used to convert a string to a floating-point value, but function will not report conversion errors; consider using 'strtof' instead [cert-err34-c]
negative.c:155:6: warning: 'scanf' used to convert a string to a floating-point value, but function will not report conversion errors; consider using 'strtof' instead [cert-err34-c]
negative.c:161:33: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:162:37: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:167:21: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:178:3: warning: 'scanf' used to convert a string to an integer value, but function will not report conversion errors; consider using 'strtol' instead [cert-err34-c]
negative.c:181:3: warning: 'scanf' used to convert a string to an integer value, but function will not report conversion errors; consider using 'strtol' instead [cert-err34-c]
negative.c:184:3: warning: 'scanf' used to convert a string to a floating-point value, but function will not report conversion errors; consider using 'strtof' instead [cert-err34-c]
negative.c:186:8: warning: narrowing conversion from 'int' to 'float' [bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions]
negative.c:187:8: warning: narrowing conversion from 'int' to 'float' [bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions]
negative.c:193:29: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:194:32: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:198:5: warning: misleading indentation: statement is indented too deeply [readability-misleading-indentation]
negative.c:193:3: note: did you mean this line to be inside this 'for'
negative.c:200:22: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:208:30: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:210:33: warning: 255 is a magic number; consider replacing it with a named constant [cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers]
negative.c:217:29: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:218:31: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:226:29: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:227:31: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:236:35: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:237:30: warning: statement should be inside braces [google-readability-braces-around-statements,hicpp-braces-around-statements,readability-braces-around-statements]
negative.c:247:8: warning: 'scanf' used to convert a string to an integer value, but function will not report conversion errors; consider using 'strtol' instead [cert-err34-c]
    

= 88 warnings !!!

Use (static) checkers

They can also be included in your editors (vim, Sublime Text, Visual Studio...)

Use memory checkers


#include 

int main (void)
{
    char *buffer = malloc (2);

    if (buffer != NULL)
    {

        buffer[0] = 'a';
        buffer[1] = 'b';
        free (buffer);
        buffer[0] = 'a';
    }

    return 0;
}
    

Use memory checkers


$ clang -W -Wall -g -fsanitize=address  use_mem.c
$ ./a.out
=================================================================
==65839==ERROR: AddressSanitizer: heap-use-after-free on address 0x6020000000f0 at pc 0x0001010b0f2b bp 0x7ffeeeb52740 sp 0x7ffeeeb52738
WRITE of size 1 at 0x6020000000f0 thread T0
    #0 0x1010b0f2a in main use_mem.c:14
    #1 0x7fff203a9620 in start+0x0 (libdyld.dylib:x86_64+0x15620)

0x6020000000f0 is located 0 bytes inside of 2-byte region [0x6020000000f0,0x6020000000f2)
freed by thread T0 here:
    #0 0x1011082c6 in wrap_free+0xa6 (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x492c6)
    #1 0x1010b0edf in main use_mem.c:13
    #2 0x7fff203a9620 in start+0x0 (libdyld.dylib:x86_64+0x15620)

previously allocated by thread T0 here:
    #0 0x10110817d in wrap_malloc+0x9d (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x4917d)
    #1 0x1010b0e18 in main use_mem.c:6
    #2 0x7fff203a9620 in start+0x0 (libdyld.dylib:x86_64+0x15620)

SUMMARY: AddressSanitizer: heap-use-after-free use_mem.c:14 in main
Shadow bytes around the buggy address:
  0x1c03ffffffc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c03ffffffd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c03ffffffe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c03fffffff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c0400000000: fa fa fd fd fa fa 00 00 fa fa 00 07 fa fa 00 fa
=>0x1c0400000010: fa fa 00 04 fa fa 00 00 fa fa 00 06 fa fa[fd]fa
  0x1c0400000020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
    

Use memory checkers

valgrind can be used if you don't have the source

$ clang -W -Wall use_mem.c
$ valgrind ./a.out
==2936== Memcheck, a memory error detector
==2936== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2936== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==2936== Command: ./a.out
==2936==
==2936== Invalid write of size 1
==2936==    at 0x401186: main (in /home/imaEns/jdequidt/a.out)
==2936==  Address 0x4a41040 is 0 bytes inside a block of size 2 free'd
==2936==    at 0x48369AB: free (vg_replace_malloc.c:530)
==2936==    by 0x401181: main (in /home/imaEns/jdequidt/a.out)
==2936==  Block was alloc'd at
==2936==    at 0x483577F: malloc (vg_replace_malloc.c:299)
==2936==    by 0x40115A: main (in /home/imaEns/jdequidt/a.out)
==2936==
==2936==
==2936== HEAP SUMMARY:
==2936==     in use at exit: 0 bytes in 0 blocks
==2936==   total heap usage: 1 allocs, 1 frees, 2 bytes allocated
==2936==
==2936== All heap blocks were freed -- no leaks are possible
==2936==
==2936== For counts of detected and suppressed errors, rerun with: -v
==2936== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
    

Use debugger

  • gdb (with gcc) or lldb (with clang)
  • you need tom compile with -g to enable debug info

Use debugger

Command Shortcut Comment
run [args] r start the program
quit q
CTRL+C break

Use debugger

Command Shortcut Comment
break [where] b where = line number or function
disable # # bp number
enable # (once) # bp number
info break informations

Use debugger

Command Shortcut Comment
watch (expr ou cond) b surveillance
break # if cond bp + watch
continue c # until bp #
next # n # jump # lines
step # s # jump # lines (goes inside func)
finish continue until func ends

Use debugger

Command Shortcut Comment
bt (full) display stack
up / down stack navigation
print expr p expr display expr

Focus on memory

  • text: instructions
  • data: static, global init. data
  • bss: static, global uninit. data
  • heap: zone for dynamically allocated data
  • stack: zone for temp (=local) data

Focus on memory


$ size a.out
   text    data     bss     dec     hex filename
   1587     588      12    2187     88b a.out
    

Focus on memory


int incr()
{
  static int c=0;
  return c++;
}

int func()
{
  return incr();
}

int main()
{
  int i = func();
  return i;
}
    

Focus on memory


$ gcc -g tt_gdb.c
$ lldb a.out
(lldb) target create "a.out"
(lldb) b incr
Breakpoint 1: where = a.out`incr + 4 at tt_gdb.c:4:11, address = 0x0000000100003f64
(lldb) run
Process 85292 launched: '/Users/jeremie/tmp/a.out' (x86_64)
Process 85292 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
    frame #0: 0x0000000100003f64 a.out`incr at tt_gdb.c:4:11
   1    int incr()
   2    {
   3      static int c=0;
-> 4      return c++;
   5    }
   6
   7    int func()
Target 0: (a.out) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
  * frame #0: 0x0000000100003f64 a.out`incr at tt_gdb.c:4:11
    frame #1: 0x0000000100003f89 a.out`func at tt_gdb.c:9:10
    frame #2: 0x0000000100003fa4 a.out`main at tt_gdb.c:14:11
    frame #3: 0x00007fff203a9621 libdyld.dylib`start + 1
    

Test your code

You need to test every functions you write (= unit testing) because

  • ... even the simplest may not be bug-proof
  • ... memory errors could lead to errors elsewhere in your code
  • ... sometimes some refactoring operations could lead to new bugs

Test your code

You need to be sure that your code improve over time and do not regress

Test your code

Example


Ptcellule allouer ()
{
    return ((Ptcellule)malloc (sizeof (Cellule)));
}
    

should return something != NULL

Test your code

Example -- how to test


#include 
// [...]
int main()
{
  assert( NULL != allouer() )

  return 0;
}
    

if everything is OK, no errors

otherwise Assertion 'NULL != allouer()' failed

Test your code

Example -- limitations

you have to add lots of asserts that can hinders performances

Solution: is to build another executable for testing

Test your code

Example -- check.h

small library that enables unit testing

Test your code


int init_rn_generator ()
{
    static unsigned int is_rn_seed_loaded = 0;
    if (is_rn_seed_loaded == 0)
    {
        srand (time (NULL));
        is_rn_seed_loaded++;
    }
    return is_rn_seed_loaded;
}

void init_array (int t[], unsigned int N, unsigned int V_MAX)
{
    for (unsigned int i = 0; i < N; i++)
    {
        t[i] = rand () % V_MAX;
    }
}
    

Test your code


#include 

START_TEST (test_rn)
{
    ck_assert (init_rn_generator () == 1);
}
END_TEST

START_TEST (test_init)
{
    const unsigned int V = 10;
    const unsigned int T = 5;
    int                array[T];
    init_array (array, T, V);
    for (unsigned int i = 0; i < T; i++)
    {
        ck_assert (array[i] >= 0);
        ck_assert (array[i] < (int)V);
    }
}
END_TEST

Suite *sort_suite (void)
{
    Suite *s       = suite_create ("SortAlgorithms");
    TCase *tc_core = tcase_create ("Core");

    tcase_add_test (tc_core, test_rn);
    tcase_add_test (tc_core, test_init);
    suite_add_tcase (s, tc_core);

    return s;
}

int main (void)
{
    int      no_failed = 0;
    Suite *  s         = sort_suite ();
    SRunner *runner    = srunner_create (s);
    srunner_run_all (runner, CK_NORMAL);
    no_failed = srunner_ntests_failed (runner);
    srunner_free (runner);
    return (no_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
    

Test your code


./build/unit_tests
Running suite(s): SortAlgorithms
100%: Checks: 2, Failures: 0, Errors: 0
    

you can add some options (lcov) to check the coverage of your tests

a complete exmaple is available on gitlab Ima_Prog_Adv

Test your code

Automation - Makefile


# Building rules
default: $(EXEC_APPLI)

# Main app
main: $(EXEC_APPLI)

# Unit Tests
tests: $(EXEC_TESTS) coverage

# Code formatting and Duplicates
lint: format pmd

# Static Checkers
checkers: tidy rats cppcheck

format:
    @./scripts/run-clang-format.py -r $(SRC_DIR) $(INCL_DIR) $(TEST_DIR)
pmd:
    pmd cpd --minimum-tokens 100 --language c --files $(SRC_DIR) \
        $(TEST_DIR) $(INCL_DIR)

tidy:
    clang-tidy --quiet -checks='*' -header-filter="^include" \
        $(SRC_DIR)/*.c -- -I$(OS_DIR) -I$(INCL_DIR)

rats:
    rats -l c -w 3 $(SRC_DIR)/*.c $(TEST_DIR)/*.c

cppcheck:
    cppcheck --enable=warning,style,portability $(SRC_DIR)/*.c $(TEST_DIR)/*.c

coverage:
    $(EXEC_TESTS)
    gcov -f -b $(BUILD_DIR)/
    lcov --directory $(BUILD_DIR) --base-directory $(BUILD_DIR)  -c \
        -o $(REPORT_DIR)/cov.info
    genhtml $(REPORT_DIR)/cov.info -o $(REPORT_DIR)
    

Automation - gitlab


stages:
    - codestyling
    - check
    - build
    - test
    - coverage
    - clean

job:codestyling:
    stage: codestyling
    script: ./scripts/run-clang-format.py -r src includes tests

job:check:tidy:
    stage: check
    script: clang-tidy src/*.c -- -Iincludes
    when: always

job:check:cppcheck:
    stage: check
    script: cppcheck --enable=warning,style,portability src/*.c
    when: always

job:build:
    stage: build
    script:
        - mkdir build
        - make main
    when: always
    artifacts:
        paths:
            - build/

job:test:
    stage: test
    script:
        - make tests
        - build/tri_selec_tests
    when: on_success
    artifacts:
        paths:
            - build/

job:coverage:
    stage: coverage
    script:
        - llvm-cov gcov -f -b build/*
        - lcov --directory build --base-directory .  -c -o cov.info
        - mkdir report
        - genhtml cov.info -o report
    coverage: '/^\s*lines\S*\s*(\d+(?:\.\d+)?%)\s*/'
    when: on_success
    dependencies:
        - job:test
    artifacts:
        paths:
            - report/
    

Automation - gitlab

Conclusion

Take-away messages

  • Knowing compilation theory helps to find your errors / bugs
  • Be humble: We make lots of mistakes, use automatic tools to detect them (clang options, static checkers...)
  • Be hungry: practice ! try new things, trials and errors are important
  • Be smart: avoid repetitive tasks, rely on automation (make, gitlab, git hooks...) or on your editor (vim, VS Code, Sublime Text can handle clang-format, clang-tidy, cppcheck...)

Vim with clang-tidy and cppcheck

Vim config


set nocompatible                            "be iMproved, required
filetype off                                "required

set rtp+=~/.vim/bundle/Vundle.vim

call vundle#begin()
Plugin 'VundleVim/Vundle.vim'               "Gestionnaire de paquets
Plugin 'w0rp/ale'                           "Syntax analyzer
Plugin 'universal-ctags/ctags'              "Ctags pour symboles
Plugin 'taglist.vim'                        "gestion tags du code source
Plugin 'rhysd/vim-clang-format'             "clang-format
call vundle#end()                           "required

filetype plugin indent on                   "required