From 74795d062359f36363db1beb4762721b48477179 Mon Sep 17 00:00:00 2001 From: Orestis Malaspinas <orestis.malaspinas@hesge.ch> Date: Tue, 5 Jan 2021 21:28:07 +0100 Subject: [PATCH] added asserts and void * --- slides/base_8.md | 192 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 slides/base_8.md diff --git a/slides/base_8.md b/slides/base_8.md new file mode 100644 index 0000000..e09c4c7 --- /dev/null +++ b/slides/base_8.md @@ -0,0 +1,192 @@ +--- +title: Base VIII +date: 2020-01-06 +--- + +# Tests unitaires + +- Compilation `!=` bon fonctionnement! +- Toujours tester vos programmes. +- Tester les fonctionnalités une par une $\Rightarrow$ **tests unitaires**. +- Plus le code est modulaire, plus il est simple à tester. +- Plus le code est testé, moins il aura contiendra de bugs. + +*Testing shows the presence, not the absence of bugs.* E. W. Dijkstra. + +# Assertions (1/3) + +```C +#include <assert.h> +void assert(int expression); +``` + +## Qu'est-ce donc? + +- Macro permettant de tester une condition lors de l'exécution d'un programme: + - Si `expression == 0`{.C} (condition fausse), `assert()`{.C} affiche un message d'erreur sur `stderr`{.C} et termine l'exécution du programme. + - Sinon l'exécution se poursuit normalement. + +## À quoi ça sert? + +- Permet de réaliser des tests unitaires. +- Permet de tester des conditions catastrophiques d'un programme. +- **Ne permet pas** de gérer les erreurs. + +# Assertions (2/3) + +\footnotesize + +## Exemple + +:::::::::::::: {.columns} + +::: {.column width="52%"} +```C +#include <assert.h> +#include <stdlib.h> +void copy(int *src, int *dst, int n) { + // identique à assert(src != NULL) + assert(src); assert(dst); + assert(n >= 0); + + for (int i = 0; i < n; ++i) { + dst[i] = src[i]; + } +} +void fill(int *dst, int n, int val) { + assert(dst && + "problem with allocated mem"); + assert(n >= 0 && + "n is the size of dst"); + + for (int i = 0; i < n; ++i) { + dst[i] = val; + } +} +``` +::: +::: {.column width="48%"} + +```C +int main(int argc, char **argv) { + int size = 10; + int *src = malloc(size * + sizeof(int)); + fill(src, size, 0); + int *dst = malloc(size * + sizeof(int)); + copy(src, dst, size); + + return EXIT_SUCCESS; +} +``` +::: +:::::::::::::: + +# Assertions (3/3) + +## Cas typiques d'utilisation + +- Vérification de la validité des pointeurs (typiquement `!= NULL`{.C}). +- Vérification du domaine des indices (dépassement de tableau). + +## Bug vs erreur de *runtime* + +- Les assertions sont là pour détecter les bugs (erreurs d'implémentation). +- Les assertions ne sont pas là pour gérer les problèmes externes au programme (allocation mémoire qui échoue, mauvais paramètre d'entrée passé par l'utilisateur, ...). + +# Généricité en C + +## Problématique + +* En C on doit écrire chaque algorithme/structures de données pour des types précis (arbres, tri, ...). +* Duplication du code pour chaque type possible et imaginable. + +## Solution: `void *` + +* En général, un pointeur connaît son **adresse** et le **type** des données sur lesquelles il pointe. + + ```C + int *a = malloc(sizeof(*a)); + int *b = malloc(sizeof(int)); + ``` +* Un `void *` le connaît **que** son adresse, au programmeur de pas faire n'importe quoi. +* Vous avez déjà utilisé des fonctions utilisant des `void *`{.C} + + ```C + void *malloc(size_t size); + void free(void *); + ``` + +# Attention danger + +* Ne permet pas au compilateur de vérifier les types. +* Les données pointées n'ayant pas de type, il faut déréférencer avec précaution: + + ```C + int a = 2; + void *b = &a; //jusqu'ici tout va bien + double c = *b; // argl! + ``` +* À la programmeuse de faire attention à ce qu'elle fait. + +# Cas d'utilisation (1/3) + +## Que fait cette fonction? + +\footnotesize + +```C +void *foo(void *tab, int n_items, int s_items, + bool (*bar)(void *, void *)) { + if (n_items <= 0 || s_items <= 0 || NULL == tab) { + return NULL; + } + void *elem = tab; + for (int i = 1; i < n_items; ++i) { + // void pointer arithmetics is illegal in C + // (gcc is ok though) + void *tmp_elem = (void *)((char *)tab + i*s_items); + + if (bar(elem, tmp_elem)) { + elem = tmp_elem; + } + } + return elem; +} +``` + +# Cas d'utilisation (2/3) + +Avec un tableau d'entier + +```C +bool cmp_int(void *a, void *b) { + return (*(int *)a < *(int *)b); +} + +int main() { + int tab[] = {-1, 2, 10, 3, 8}; + int *a = foo(tab, 5, sizeof(int), cmp_int); + printf("a = %d\n", *a); +} +``` + +# Cas d'utilisation (3/3) + +Avec un tableau d'entier + +```C +bool cmp_dbl(void *a, void *b) { + return (*(double *)a < *(double *)b); +} + +int main() { + double tab[] = {-1.2, 2.1, 10.5, 3.6, 18.1}; + double *a = foo(tab, 5, sizeof(double), cmp_dbl); + printf("a = %f\n", *a); +} +``` + + + -- GitLab