Skip to main content
Compilation Toolchain
Compilation Toolchain
IOT
2h

Code Quality and Debugging

Writing Better Programs

Write Readable Programs

  • Use formatting conventions / best practices
  • Use clang-format for automatic formatting
clang-format -i fichier.c

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

Before Formatting

#include <stdio.h>
#include <stdlib.h>

#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;
    }
}

After Formatting

#include <stdio.h>
#include <stdlib.h>

#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;
        }
}

Principles

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

Applying DRY with PMD

Duplicates search with pmd:

$ 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 ) //verifie que la portion d'image est bien dans l'ellipse

Enable Warnings

#include <stdio.h>

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

    return 0;
}
$ 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

Static checkers allow you to detect common mistakes:

  • clang-tidy (CERT guidelines)
  • cppcheck with MISRA, CERT add-ons (embedded programming guidelines)
  • rats
  • oclint

Example: From a Student Project

$ gcc negative.c
# no errors, no warnings

With warnings enabled:

$ gcc negative.c -c -W -Wall -Wextra
# 6 warnings

With clang-tidy:

$ clang-tidy --quiet -checks='*' negative.c --
# 88 warnings!

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

Use Memory Checkers

#include <stdlib.h>

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

    if (buffer != NULL)
    {

        buffer[0] = 'a';
        buffer[1] = 'b';
        free (buffer);
        buffer[0] = 'a';  // Use after free!
    }

    return 0;
}

With AddressSanitizer:

$ clang -W -Wall -g -fsanitize=address use_mem.c
$ ./a.out
=================================================================
==65839==ERROR: AddressSanitizer: heap-use-after-free on address 0x6020000000f0
WRITE of size 1 at 0x6020000000f0 thread T0
    #0 0x1010b0f2a in main use_mem.c:14
...

Valgrind can be used if you don’t have the source:

$ clang -W -Wall use_mem.c
$ valgrind ./a.out
==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
...

Use Debugger

Use gdb (with gcc) or lldb (with clang). Compile with -g to enable debug info.

Basic Commands

CommandShortcutComment
run [args]rstart the program
quitq
CTRL+Cbreak

Breakpoints

CommandShortcutComment
break [where]bwhere = line number or function
disable ## bp number
enable # (once)# bp number
info breakinformations

Watching

CommandShortcutComment
watch (expr ou cond)surveillance
break # if condbp + watch
continuec #until bp #
next #n #jump # lines
step #s #jump # lines (goes inside func)
finishcontinue until func ends

Stack Navigation

CommandShortcutComment
bt (full)display stack
up / downstack navigation
print exprp exprdisplay expr

Focus on Memory

Memory layout:

  • text: instructions
  • data: static, global initialized data
  • bss: static, global uninitialized data
  • heap: zone for dynamically allocated data
  • stack: zone for temporary (local) data
$ size a.out
   text    data     bss     dec     hex filename
   1587     588      12    2187     88b a.out

Test Your Code

You need to test every function 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

You need to be sure that your code improves over time and does not regress.

Example Test with check.h

#include <check.h>

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;
}

Output:

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

You can add options (lcov) to check the coverage of your tests.

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 CI

.gitlab-ci.yml
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/project_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/

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 Configuration Example

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