Analyse et programmation 1 Les pièges du langage C 1
Thèmes abordés Les pièges? Une petite collection de pièges Est-il possible d écrire un programme fonctionnel en C? Référence utilisée : «Les pièges du langage C», Andrew Koenig 2
Les pièges De quoi s agit-il? Principe du piège Semble attrayant et rassurant au premier abord. Cause quelques soucis quand on s est fait prendre. En programmation Un élément de code source qui semble tout à fait juste Même à la dixième relecture. Mais qui ne fonctionne pas. A cause d un détail de syntaxe quasiment invisible. 3
Les pièges Comment s en protéger? Pour les souris Eviter les pièges Se nourrir dans des zones non fréquentées par les humains. Vérifier l absence de piège : tester le bout de fromage. Attendre qu une autre souris y ait goûté. Limites de la méthode Le piège ne se déclenche pas à tous les coups. En programmation Eviter les pièges Utiliser des instructions ayant peu de risques de pièges. Vérifier l absence de piège Apprendre à les reconnaitre. Programmer très attentivement pour les éviter. Tester rigoureusement le code. 4
Les pièges du préprocesseur Les espaces jouent un rôle prépondérant dans les définitions de macros #include <stdio.h> #include <stdlib.h> #define f (x) x + 1 Espace après f : f est une macro sans paramètres! Message d erreur généré ici : Identificateur x non déclaré printf("resultat:%d\n", f(1)); Résolution Vigilance particulière dans les définitions de macros. Être toujours prêt à mettre en doute le code généré par macro. 5
Les pièges du préprocesseur Les macros ne sont pas des fonctions (1/2) #include <stdio.h> #include <stdlib.h> #define ABS(x) (x >= 0? x : -x) printf("resultat:%d\n", ABS(5-8)); Toujours bien parenthéser les macros. Expansion de ABS(5 8): (5 8 >= 0? 5 8 : -5 8) Résultat inattendu : -13 6
Les pièges du préprocesseur Les macros ne sont pas des fonctions (2/2) #define ABS(x) ((x) >= 0? (x) : -(x)) int i = 5; printf("resultat:%d\n", ABS(++i)); Penser qu une macro n est pas une fonction. Expansion de ABS(++i): ((++i) >= 0? (++i) : -(++i)) Résultat inattendu : 7 Les arguments peuvent être évalués plusieurs fois! 7
Les pièges du préprocesseur Les macros ne sont pas des instructions #define rendre_positif(x) if (x < 0) x = -x int i = 5; if (i < 0) rendre_positif(i); else Expansion : if (i < 0) if (i < 0) i = -i; else printf("i est deja positif\n"); printf("i est deja positif.\n"); Résultat inattendu : Cette instruction printf n est jamais exécutée! Eviter ce genre de macros. Préférer les fonctions. 8
Les pièges du préprocesseur Les macros ne sont pas des définitions de type #define PCHAR char * Expansion : char * texte1, texte2; PCHAR texte1, texte2; texte1 = "Hello"; texte2 = "World!\n"; printf(texte1); printf(texte2); Résultat inattendu : texte2 contient un char, et non pas l adresse d un texte. Erreur fatale à l exécution! Utiliser typedef plutôt que les macros pour créer des types. 9
Les pièges lexicaux Le plus répandu #include <stdio.h> #include <stdlib.h> char c; L opérateur d affectation est utilisé à la place de la comparaison do c = getchar(); while (c == ' ' c = '\t' c == '\n'); Être très vigilant. Placer les constantes à gauche. Certains compilateurs peuvent émettre un Warning. Résultat inattendu : boucle sans fin! 10
Les pièges lexicaux Le plus sournois L opérateur donne ici l impression de fonctionner comme int i; i = 0; if (i++ > -10 i++ < 10) printf("resultat:%d\n", i); i = 0; if (i++ > -10 i++ < 10) printf("resultat:%d\n", i); Différence sournoise : même si cela fonctionne dans ce cas, avec, les deux opérandes sont toujours évalués! Bien connaître la syntaxe des opérateurs bit à bit et logiques. 11
Les pièges lexicaux Combinaison d opérateurs déroutante #include <stdio.h> #include <stdlib.h> int i = 1, j = 10, k; k = i---j; printf("i:%d, j:%d, k:%d\n", i, j, k); i---j est interprété comme (i--) - j Penser que l analyse lexicale du C est gloutonne. Eviter d utiliser des formes ambigües. Quel sera le résultat? i = 0, j = 10, k = -9 ou i = 1, j = 9, k = -8 12
Les pièges lexicaux Les constantes entières #include <stdio.h> #include <stdlib.h> Mathématiquement, dans le système décimal, 123 et 0123 désignent le même nombre. int i = 123, j = 0123; printf("i:%d, j:%d\n", i, j); En langage C, une constante qui commence par 0 est considérée en base octale. Donc 123!= 0123 Connaissances rigoureuses des conventions du C requises. 13
Les pièges lexicaux Les constantes caractère et chaînes de caractères #include <stdio.h> #include <stdlib.h> printf("hello World!%s", '\n'); printf('\n'); Certains compilateurs émettent un warning pour le 2 ème cas. Très grande rigueur nécessaire. Un caractère est passé au lieu d une chaîne de caractère. Résultat : affichage incorrect ou plantage du programme 14
Les pièges syntaxiques Les priorités des opérateurs s int registre, masque; registre = 0x80; masque = 0x80; if (registre & masque!= 0) printf("le bit b7 du registre est a 1\n"); else printf("le bit b7 du registre est a 0\n"); User et abuser des parenthèses. On pourrait penser que ce test fait un et bit à bit de registre et masque, et teste si le résultat est différent de 0. Attention, l opérateur!= est prioritaire. Résultat, ce programme affiche à tort que le bit b7 est à 0 15
Les pièges syntaxiques Les structures de contrôle qui ne fonctionnent plus int i; i = 100; if (i < 0); printf("i est negatif.\n"); while (i > 0); i--; Ce programme semble correct. Ce programme affiche que i est négatif. Puis il boucle à l infini. Seule une rigueur extrême permet d éviter ces problèmes. 16
Les pièges syntaxiques L instruction switch char reponse; printf("voulez vous vraiment effacer ce fichier (o:oui, n:non)? "); reponse = getchar(); switch (reponse) default: printf("reponse incorrecte.\n"); case 'n': printf("operation abandonnee.\n"); case 'o': EraseFile("ListeClient.txt"); printf("le fichier a été efface.\n"); Toujours penser switch => break. Ce programme a une structure bien lisible, mais il manque les break. Résultat inattendu : Le fichier sera effacé quelle que soit la réponse! 17
Les pièges syntaxiques Les appels de fonction char reponse; reponse = getchar; switch (reponse) default: printf("reponse incorrecte.\n"); break; case 'n': printf("operation abandonnee.\n"); break; case 'o': EraseFile("C:\listeClient.txt"); printf("fichier effacé.\n"); break; Ce programme semble correct. Mais il manque () pour l appel de getchar. Résultat inattendu : La réponse n est pas saisie. Elle dépend de l adresse de getchar! Certains compilateurs génèrent un warning. Activer tous les warning possibles. Rigueur, toujours! 18
Les pièges syntaxiques Le else qui pend #include <stdio.h> #include <stdlib.h> int x, y, z; if (x == 0) if (y == 0) printf("erreur.\n"); else z = y / x; printf("z:%g\n", z); User des accolades à la moindre ambigüité. Toujours s interroger à propos du else après un if. Contrairement à ce que la présentation laisse imaginer, ce else se rapporte au 2 ème if. Résultat inattendu : si x vaut 0 et y différent de 0, il y a une division par zéro! 19
Les pièges sémantiques Ca fonctionnait hier, ça ne fonctionne plus aujourd hui int x, y, z, somme; printf("valeur de x:"); scanf("%d", &x); printf("valeur de y:"); scanf("%d", &y); /* printf("valeur de z:"); scanf("%d", &z); */ somme = x + y + z; printf("somme:%d\n", somme); Certains compilateurs émettent un warning. Rigueur Suite à une modification du programme, la variable z est utilisée sans avoir été initialisée. Ce programme fonctionnera très bien si le hasard fait que z vaut 0, et ne plus fonctionner du jour au lendemain! 20
Les pièges sémantiques Problème implicite #include <stdio.h> #include <stdlib.h> Ce programme semble correct. Mais il manque le prototype pour sqrt. double resultat; resultat = sqrt(100.0); printf("resultat:%g\n", resultat); Résultat inattendu : la fonction sqrt est considérée comme retournant int! (Visual C++ 2010: 1.07958e+009) (Visual C++ 2013: Erreur de compilation) S assurer que le prototype d une fonction est spécifié avant l utilisation. Certains compilateurs émettent un warning. 21
Les pièges sémantiques Un résultat surprenant long factorielle(long n) long i; long resultat; resultat = 1; for (i = 2; i <= n; i++) resultat *= i; Ce programme semble correct. Mais il manque l instruction return pour renvoyer le résultat à l appelant. double d; printf("factorielle 5: %d\n", factorielle(5)); Certains compilateurs émettent un warning. Résultat inattendu : La valeur retournée est quelconque! 22
Les problèmes de portabilité La taille des types entiers int i = 20000; int j = 2 * i; printf("resultat:%d\n", j); Avec certaines compilateurs, int est un format 16 bits limité à -32768..+32767 Utiliser des typedefs pour préciser le format supposé des types. typedef long int32; typedef short int16; typedef char int8; 23
Est-il possible d écrire un programme fonctionnel en C? Quelques recommandations pour surmonter les difficultés Compiler en mode C++ Activer le plus haut niveau de warning possible Limiter l usage du préprocesseur Bannir toutes les expressions ambigües Appliquer des stratégies de recherche d erreurs Utiliser le debugger Mettre en place des traces avec printf. Effectuer une recherche dichotomique dans le code. Que faire en cas de désespoir? Relecture critique du code. Être prêt à tout remettre en question Même le compilateur peut être buggé! Essayer de mettre en évidence le problème observé. Eventuellement analyser le code assembleur généré. 24
Est-il possible d écrire un programme fonctionnel en C? Contexte industriel Des application difficiles écrites en assembleur existent. L écriture en langage C est nettement plus facile. Le langage C permet d écrire des applications complexes A condition d avoir les connaissances nécessaires. De structurer correctement les programmes. De réaliser la programmation de façon attentive. 25
Conclusions Le langage C comporte de nombreux pièges. Mieux vaut prévenir que guérir. Un ingénieur averti en vaut deux. 26
Vos questions 27
28