C: conversion de type lors du passage d’un argument à un appel de fonction

Du langage de programmation C 2e édition:

Comme un argument d’un appel de fonction est une expression, les conversions de types ont également lieu lorsque des arguments sont passés à function. En l’absence d’un prototype de fonction, char et short deviennent int et flottent deviennent doubles.

En lisant le texte, j’ai l’impression que, sauf si vous spécifiez explicitement le type d’argument en utilisant le prototype de fonte ou le prototype de fonction, les arguments de fonction seront toujours passés soit en tant que int, soit en double.

Afin de vérifier mon hypothèse, j’ai compilé le code suivant:

#include  main() { unsigned char c = 'Z'; float number = 3.14f; function_call(c, number); } void function_call(char c, float f) { } 

Après la compilation, je reçois les avertissements suivants:

typeconversion.c: 11: warning: types conflictuels pour ‘function_call’

typeconversion.c: 7: warning: la déclaration implicite précédente de ‘function_call’ était là

Je suppose que c et number ont tous deux été convertis en int et double lors de l’appel de la fonction, puis ont été reconvertis en char et en float. Est-ce ce qui s’est réellement passé?

Les casts sont sans importance, c’est le prototype (éventuellement implicite) qui compte.

 void foo(short s) { // do something } int main(void) { signed char c = 'a'; foo(c); // c is promoted to short by explicit prototype bar(c); // c is promoted to int by implicit prototype } void bar(int i) { // do something } 

Lorsque le livre dit “un argument d’appel de fonction est une expression”, cela signifie que les mêmes règles de promotion s’appliquent. Il serait peut-être plus facile de comprendre si vous considérez un argument de fonction comme une affectation implicite à la variable spécifiée dans le prototype de fonction. Par exemple, dans l’appel à foo() ci-dessus, il existe un short s = c implicite: short s = c .

C’est pourquoi les castes n’ont pas d’importance. Considérez l’extrait de code suivant:

 signed char c = 'a'; int i = (short) c; 

Ici, la valeur de c est d’abord promue (explicitement) puis int (implicitement). La valeur de i sera toujours un int .

En ce qui concerne char et short deviennent int et float deviennent double qui font référence aux types par défaut des prototypes de fonctions implicites. Lorsque le compilateur voit un appel à une fonction avant d’avoir vu un prototype ou la définition de la fonction, il génère automatiquement un prototype. La valeur par défaut est int pour les valeurs entières et double pour les valeurs à virgule flottante.

Si la déclaration de fonction éventuelle ne correspond pas au prototype implicite, vous recevrez des avertissements.

Vous avez une idée générale de ce qui ne va pas, mais pas exactement.

Qu’est-ce qui s’est passé, c’est que quand vous avez écrit

 function_call(c, number); 

Le compilateur a vu que vous appeliez une fonction qu’il n’avait pas encore vue et a donc dû décider quelle devrait être sa signature. Sur la base de la règle de promotion que vous avez citée plus tôt, il a encouragé char à int et float à doubler et a décidé que la signature était

 function_call(int, double) 

Puis quand il voit

 function_call(char c, float f) 

cela est interprété comme une signature pour une fonction différente portant le même nom, ce qui n’est pas autorisé en C. C’est exactement la même erreur que si vous prototypiez une fonction différemment de la façon dont vous la définissez, mais que dans ce cas, le prototype est implicitement généré par le compilateur.

Le problème est donc à l’origine de cette règle, mais l’erreur n’a rien à voir avec la conversion des valeurs entre les types.

Le compilateur se plaint d’avoir supposé que function_call est une fonction renvoyée par int, comme l’indique la norme, et vous dites ensuite qu’il s’agit d’une fonction vide. Le compilateur ne se soucie pas des arguments, sauf si vous les déclarez explicitement comme étant différents de la fonction réelle. Vous pouvez lui passer aucun argument et il ne se plaindra pas.

Vous devez toujours déclarer vos fonctions car cette erreur ne sera pas détectée si la fonction est dans d’autres modules. Si la fonction doit renvoyer un type plus volumineux que int tel que void * ou long, le transtypage vers int dans la fonction appelant sera probablement le tronquer, ce qui vous laissera avec un bogue étrange.

Tout le monde a manqué une chose. En ISO C, un prototype de syntaxe ISO remplace la promotion d’argument par défaut.

Et dans ce cas, le compilateur est autorisé à générer un code différent (!) Basé uniquement sur le style de définition. Cela vous assure la compatibilité K & R, mais vous ne pouvez pas toujours appeler entre les niveaux de langue à moins d’avoir écrit le code ISO tel que prévu par K & R ou modifié le code K & R pour afficher les prototypes ISO.

Essayez-le avec cc -S -O …

 extern float q; void f(float a) { q = a; } movl 8(%ebp), %eax movl %eax, q extern float q; void f(a) float a; { q = a; } // Not the same thing! fldl 8(%ebp) fstps q