Comportement indéfini avec des déclarations de fonctions obsolètes dans ANSI C


La norme ANSI C définit le concept d' un prototype de fonction , qui est un sous-ensemble d'une déclaration de fonction qui indique les types de paramètres d'entrée. Des prototypes ont été introduits afin d'éliminer les inconvénients des déclarations de fonctions communes.


Ainsi, la spécification d'une liste de types de paramètres entre parenthèses d'un prototype de fonction est obligatoire, sinon une telle expression sera reconnue par le compilateur comme une déclaration de fonction obsolète, ce qui peut conduire à des situations ambiguës décrites dans cet article.


Prototypes obsolètes


Une déclaration de fonction introduit le type de retour de la fonction et son identifiant dans la portée spécifiée. Notez que toutes les déclarations de fonctions ne peuvent pas être considérées comme des prototypes, mais uniquement celles qui ont une liste de types de paramètres d'entrée.


Ainsi, la première expression du code ci-dessous est une déclaration, mais pas un prototype de fonction. L'expression suivante peut à juste titre être considérée comme un prototype, car elle spécifie les types de ses paramètres:


/* #1 (   "foo") */ void foo(); /* #2 (  "bar") */ void bar(int count, const char *word); 

Définitions obsolètes


Allons directement à 1972 (l'année de sortie du langage C) et rappelons-nous comment les programmeurs de l'époque définissaient leurs fonctions. Permettez-moi de vous rappeler que la définition d'une fonction relie sa signature au bloc exécutable correspondant (corps). Ce code illustre la définition de la fonction d' add dans le style K&R:


 void add(right, left, result) int right; int left; int *result; { *result = right + left; } 

Comme vous l'avez peut-être remarqué, dans cette entrée, les parenthèses identifient la fonction, mais ne contiennent aucun type de paramètres d'entrée. Les déclarations de fonctions «classiques» décrites dans la section précédente ont la même propriété.


Situations ambiguës


Il est possible que si la nouvelle syntaxe des prototypes et des définitions de fonctions introduites par la norme ANSI C ne soit pas respectée, des situations ambiguës difficiles puissent survenir. Prenons un exemple:


 #include <stdio.h> #include <stdint.h> #include <inttypes.h> #include <limits.h> /*    "print_number" */ void print_number(); int main(void) { /*  */ print_number((double)13.359); print_number((double)9238.46436); print_number((double)18437); /*    */ print_number(UINT64_MAX); print_number("First", "Second", "Third"); print_number(NULL, "Breakfast", &print_number); } void print_number(double number) { printf(" : [%f]\n", number); } 

Nous analyserons ce programme. La fonction print_number correcte print_number déclarée sans spécifier de liste de types de paramètres, vous pouvez donc appeler cette fonction avec n'importe quel argument. Le programme a compilé sans erreur et a imprimé le résultat suivant:


 $ gcc illegal.c -o illegal -Wall $ ./illegal  : [13.359000]  : [9238.464360]  : [18437.000000]  : [0.000000]  : [0.000000]  : [0.000000] 

Notez également que même avec l'indicateur -Wall , le compilateur -Wall gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0 n'a généré aucun avertissement (mais il serait hautement souhaitable).


Fixer ce programme n'est pas difficile, ajoutez simplement le double number entre parenthèses de la déclaration de la fonction print_number sur la septième ligne, après quoi tout compilateur suivant la norme indiquera des erreurs dans la fonction main() :


 $ gcc -Wall illegal.c -o illegal illegal.c: In function 'main': illegal.c:17:22: error: incompatible type for argument 1 of 'print_number' print_number("First", "Second", "Third"); ^~~~~~~ illegal.c:7:6: note: expected 'double' but argument is of type 'char *' void print_number(double number); ^~~~~~~~~~~~ illegal.c:17:9: error: too many arguments to function 'print_number' print_number("First", "Second", "Third"); ^~~~~~~~~~~~ illegal.c:7:6: note: declared here void print_number(double number); ^~~~~~~~~~~~ illegal.c:18:22: error: incompatible type for argument 1 of 'print_number' print_number(NULL, "Breakfast", &print_number); ^~~~ illegal.c:7:6: note: expected 'double' but argument is of type 'void *' void print_number(double number); ^~~~~~~~~~~~ illegal.c:18:9: error: too many arguments to function 'print_number' print_number(NULL, "Breakfast", &print_number); ^~~~~~~~~~~~ illegal.c:7:6: note: declared here void print_number(double number); ^~~~~~~~~~~~ 

Fonctions sans paramètres


Je note également que la spécification du mot clé void entre parenthèses de prototypes et de définitions de fonctions qui ne prennent pas de paramètres est hautement souhaitable (mais pas nécessaire). Si cette règle n'est pas respectée, le compilateur ne pourra pas vérifier la correspondance des types des arguments passés lorsque la fonction est appelée avec des types valides de la définition.


 #include <stdio.h> /*    "do_something" */ void do_something(); int main(void) { /*  "do_something"       */ do_something(NULL, "Papa Johns", 2842, 1484.3355); } void do_something() { puts("I am doing something interesting right now!"); } 

Corrigez le code ci-dessus en insérant le mot clé void dans la définition et la déclaration de la fonction do_something() , sinon ce programme se compilera sans erreur. Dans cet exemple, la fonction main() est également définie avec le jeton void dans les paramètres, bien que cela ne soit pas nécessaire.


Conclusion


La rédaction de cet article a été inspirée par le livre de Stephen Prat "Langage de programmation C. Conférences et exercices. Sixième édition", et en particulier la section "Fonctions avec arguments" du cinquième chapitre.

Source: https://habr.com/ru/post/fr456682/


All Articles