Comprendre la différence entre f() et f(void) en C et C++ une fois pour toutes

Ok, donc j'ai entendu des opinions différentes sur ce sujet et je veux juste m'assurer de le comprendre correctement.

Pour c/" class="blnk">C++

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.

Pour C

déclaration void f(void); signifie que f ne prend aucun paramètre.

Déclaration void f(); signifie que la fonction f peut ou peut ne pas avoir de paramètres, et si elle le fait, nous ne savons pas ce genre de paramètres, ceux-ci sont, ou combien il y en a des. Notez qu'il N'est pas identique à ellipsis, nous ne pouvons pas utiliser va_list .

Maintenant, voici où les choses deviennent intéressantes.

Cas 1

déclaration:

void f();

définition:

void f(int a, int b, float c)
{
   //...
}

Cas 2

déclaration:

void f();

définition:

void f()
{
   //...
}

Question:

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 du tout? Ce qui se passe au moment de l'exécution?

question supplémentaire:

si je déclare f avec arguments, mais le définir sans eux, fera-t-il une différence? Devrais-je être capable d'aborder les arguments du corps de fonction?

40
demandé sur Baum mit Augen 2012-11-10 09:16:33

4 réponses

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 hold-over de pre-ANSI C, de L'époque de K&R C. La seule raison d'utiliser une déclaration de style ancien 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 on ne peut pas l'enlever sans casser les binaires. Le standard C99 commentaires:

6.11.6 declarateurs de fonctions

l'utilisation de La fonction declarators avec des parenthèses vides (pas de prototype-paramètre de format de type declarators) est une caractéristique obsolescente.

recommandation: je suggère de compiler tout le code C dans GCC / Clang avec -Wstrict-prototypes et -Wmissing-prototypes , en outre l'habituel -Wall -Wextra .

Ce qui arrive

void f(); // declaration
void f(int a, int b, float c) { } // ERROR

la déclaration n'est pas d'accord avec le corps de fonction! C'est en fait une erreur temps de compilation , et c'est parce que vous ne pouvez pas avoir un argument float dans une fonction sans un 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 en utilisant certains 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 double . Ainsi, la définition de f() :

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 fonction déclarant que ne fait pas partie d'une définition de fonction et qui contient un vide liste d'identificateurs, la liste de paramètres ne doit pas comporter d'ellipse terminator et le type de chaque le paramètre doit être compatible avec le type résultant de l'application de la les promotions d'arguments par défaut.

Réponse 1

Ce qui se passe au moment de la compilation dans les cas 1 et 2 lorsque nous appelons f avec les bons arguments, les faux arguments et sans arguments? Ce qui se passe au moment de l'exécution?

quand vous appelez f() , les paramètres sont promus en utilisant les promotions par défaut. Si les types promus correspondent aux types de paramètres réels pour f() , alors tout est bon. S'ils ne correspondent pas, il compilera probablement mais vous obtiendrez certainement un comportement non défini.

"un comportement Indéfini" est spec-parler de "nous ne faisons aucune garantie quant à ce qui va se passer."Peut-être que ton programme va s'effondrer, peut-être que ça va marcher, peut-être qu'il invitera ta belle-famille à dîner.

il y a deux façons d'obtenir des diagnostics au moment de la compilation. Si vous avez un compilateur sophistiqué avec des capacités d'analyse statique inter-modules, alors vous obtiendrez probablement un message d'erreur. Vous pouvez également obtenir des messages pour des déclarations de fonction 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).

réponse 2

si je déclare f avec des arguments, mais le définir sans eux, cela fera-t-il une différence? Devrais-je être capable d'aborder les arguments du corps de fonction?

il ne devrait pas compiler.

Exceptions

il y a en fait deux cas dans lesquels les arguments de fonction sont autorisés à en désaccord avec la définition de la fonction.

  1. il est normal de passer char * à une fonction qui attend void * , et vice versa.

  2. 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ésentative dans les deux types (i.e., il n'est pas négatif, et pas hors de portée du type signé).

notes

1 : il est possible que char favorise à unsigned int , mais cela est très rare.

51
répondu Dietrich Epp 2015-06-11 19:44:12

L'ensemble est vraiment un point discutable si vous utilisez C99 ou plus tard (et, à moins que vous ne soyez coincé sur un vieux système embarqué ou quelque chose comme ça, vous probablement devrait utiliser quelque chose de plus moderne).

C99 / C11 section 6.11.6 Future language directions, Function declarators states:

l'utilisation de declarateurs de fonctions avec des parenthèses vides (et non des declarateurs de type de paramètre de format prototype) est une caractéristique obsolescente.

par conséquent, vous devriez éviter d'utiliser des choses comme void f(); tout à fait.

s'il faut des paramètres, listez-les, formant un bon prototype. Si non, us void pour indiquer définitivement qu'il ne prend aucun paramètre.

6
répondu paxdiablo 2016-02-29 05:59:51

En C++, f () et F (nul) est le même

In 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é en f(nul)

3
répondu rs2012 2012-11-10 10:06:38

en C pur, il en résulte l'erreur: error C2084: function 'void __cdecl f(void )' already has a body

void f(void);
void f( );

int main() {
  f(10);
  f(10.10);
  f("ten");

  return 0;
}

void f(void) {

}

void f( ) {

}

.

 fvoid.c line(19) : error C2084: function 'void __cdecl f(void )' already has a body

mais en C++ pur, il compilera sans erreur.

fonctions de surcharge (c++ seulement, C n'a pas de surcharge)

vous surchargez un nom de fonction f en déclarant plus d'une fonction avec le nom f dans la même portée. Les déclarations de f doivent être différentes les unes des autres par rapport aux déclarations types et/ou le nombre d'arguments dans la liste d'arguments. Lorsque vous appelez une fonction surchargée appelée f, la fonction correcte est sélectionnée en comparant la liste d'arguments de l'appel de fonction avec la liste de paramètres de chacune des fonctions candidates surchargées avec le nom F.

exemple:

#include <iostream>
using namespace std;

void f(int i);
void f(double  f);
void f(char* c);


int main() {
  f(10);
  f(10.10);
  f("ten");

  return 0;
}

void f(int i) {
  cout << " Here is int " << i << endl;
}
void f(double  f) {
  cout << " Here is float " << f << endl;
}

void f(char* c) {
  cout << " Here is char* " << c << endl;
}

sortie:

 Here is int 10
 Here is float 10.1
 Here is char* ten
-1
répondu Software_Designer 2012-11-10 11:50:47