D'accord, j'ai donc entendu différentes opinions à ce sujet et je veux juste m'assurer de bien le comprendre.
Les déclarations void f();
et void f(void);
signifient exactement la même chose, la fonction f
ne prend aucun paramètre. Idem pour les définitions.
La déclaration void f(void);
signifie que f
ne prend aucun paramètre.
La déclaration void f();
signifie que la fonction f
peut ou non avoir des paramètres, et si c'est le cas, nous ne savons pas de quel type de paramètres il s'agit, ni combien il y en a. Notez que ce n'est PAS la même chose que Ellipsis, nous ne pouvons pas utiliser va_list
.
Maintenant, c'est là que les choses deviennent intéressantes.
Déclaration:
void f();
Définition:
void f(int a, int b, float c)
{
//...
}
Déclaration:
void f();
Définition:
void f()
{
//...
}
Que se passe-t-il au moment de la compilation dans les cas 1 et 2 lorsque nous appelons f
avec les bons arguments, les mauvais arguments et aucun argument? Que se passe-t-il au moment de l'exécution?
Si je déclare f
avec des arguments, mais que je le définis sans eux, cela fera-t-il une différence? Dois-je être en mesure de traiter les arguments du corps de la fonction?
Plus de terminologie (C, pas C++): un prototype pour une fonction déclare les types de ses arguments. Sinon, la fonction n'a pas de prototype.
void f(); // Declaration, but not a prototype
void f(void); // Declaration and prototype
void f(int a, int b, float c); // Declaration and prototype
Les déclarations qui ne sont pas des prototypes sont des restes de pré-ANSI C, du temps de K&R C. La seule raison d'utiliser une déclaration à l'ancienne est de maintenir la compatibilité binaire avec l'ancien code. Par exemple, dans Gtk 2, il y a une déclaration de fonction sans prototype - elle est là par accident, mais elle ne peut pas être supprimée sans casser les binaires. La norme C99 commente:
6.11.6 Déclarateurs de fonctions
L'utilisation de déclarateurs de fonctions avec des parenthèses vides (et non des déclarants de type paramètre de type prototype) est une fonction obsolète.
Recommandation: Je suggère de compiler tout le code C dans GCC/Clang avec -Wstrict-prototypes
Et -Wmissing-prototypes
, En plus de l'habituel -Wall -Wextra
.
void f(); // declaration
void f(int a, int b, float c) { } // ERROR
La déclaration n'est pas d'accord avec l'organe de fonction! Il s'agit en fait d'une erreur de temps de compilation , et c'est parce que vous ne pouvez pas avoir d'argument float
dans une fonction sans prototype. La raison pour laquelle vous ne pouvez pas utiliser un float
dans une fonction non prototypée est que lorsque vous appelez une telle fonction, tous les arguments sont promus à l'aide de certaines promotions par défaut. Voici un exemple fixe:
void f();
void g()
{
char a;
int b;
float c;
f(a, b, c);
}
Dans ce programme, a
est promu int
1 et c
est promu en double
. La définition de f()
doit donc être:
void f(int a, int b, double c)
{
...
}
Voir C99 6.7.6, paragraphe 15,
Si un type a une liste de types de paramètres et que l'autre type est spécifié par un déclarant de fonction qui ne fait pas partie d'une définition de fonction et qui contient une liste d'identificateurs vide, la liste de paramètres ne doit pas avoir de terminateur Ellipsis et le type de chaque paramètre doit être compatible avec le type résultant de l'application des promotions d'argument par défaut.
Que se passe-t-il au moment de la compilation dans les cas 1 et 2 lorsque nous appelons
f
avec les bons arguments, les mauvais arguments et aucun argument? Que se passe-t-il au moment de l'exécution?
Lorsque vous appelez f()
, les paramètres sont promus à l'aide des promotions par défaut. Si les types promus correspondent aux types de paramètres réels pour f()
, alors tout va bien. S'ils ne correspondent pas, il sera probablement compilé mais vous obtiendrez certainement un comportement non défini.
"Comportement indéfini" est un langage courant pour "nous ne garantissons pas ce qui va se passer". Peut-être que votre programme va planter, peut-être que cela fonctionnera bien, peut-être qu'il invitera vos beaux-parents à dîner.
Il existe deux façons d'obtenir des diagnostics au moment de la compilation. Si vous disposez d'un compilateur sophistiqué avec des capacités d'analyse statique inter-modules, vous obtiendrez probablement un message d'erreur. Vous pouvez également obtenir des messages pour les déclarations de fonctions non prototypées avec GCC, en utilisant -Wstrict-prototypes
- que je recommande d'activer dans tous vos projets (sauf pour les fichiers qui utilisent Gtk 2).
Si je déclare
f
avec des arguments, mais que je le définis sans eux, cela fera-t-il une différence? Dois-je être en mesure de traiter les arguments du corps de la fonction?
Il ne devrait pas compiler.
Il existe en fait deux cas dans lesquels les arguments de fonction peuvent être en désaccord avec la définition de fonction.
Il est correct de passer char *
À une fonction qui attend void *
, Et vice versa.
Il est correct de passer un type entier signé à une fonction qui attend la version non signée de ce type, ou vice versa, tant que la valeur est représentable dans les deux types (c'est-à-dire qu'elle n'est pas négative et n'est pas hors de portée de la type signé).
1: Il est possible que char
soit promu en unsigned int
, Mais c'est très rare.
Le tout est vraiment un point discutable si vous utilisez C99 ou une version ultérieure (et, à moins que vous ne soyez bloqué sur un ancien système intégré ou quelque chose comme ça, vous devriez utiliser probablement quelque chose de plus moderne) .
La section C99/C11 6.11.6 Future language directions, Function declarators
Indique:
L'utilisation de déclarateurs de fonctions avec des parenthèses vides (et non des déclarants de type de paramètre au format prototype) est une fonction obsolète.
Par conséquent, vous devez éviter d'utiliser des choses comme void f();
tout à fait.
Si cela prend des paramètres, listez-les, formant un prototype approprié. Sinon, nous void
pour indiquer définitivement qu'il ne prend aucun paramètre.
En C++, f() et f(void) est le même
En C, ils sont différents et n'importe quel nombre d'arguments peut être passé en appelant la fonction f() mais aucun argument ne peut être passé dans f (void)