Pourquoi une fonction sans paramètres (par rapport à la définition de fonction réelle) se compile-t-elle?

388

Je viens de rencontrer le code C de quelqu'un que je ne comprends pas pourquoi il compile. Il y a deux points que je ne comprends pas.

Tout d'abord, le prototype de fonction n'a pas de paramètres par rapport à la définition de fonction réelle. Deuxièmement, le paramètre dans la définition de fonction n'a pas de type.

#include <stdio.h>

int func();

int func(param)
{
    return param;
}

int main()
{
    int bla = func(10);    
    printf("%d", bla);
}

Pourquoi ça marche? Je l'ai testé dans quelques compilateurs, et cela fonctionne très bien.

AdmiralJonB
la source
77
C'est K&R C. Nous avons écrit un code comme celui-ci dans les années 80 avant qu'il n'y ait des prototypes complets.
hughdbrown
6
gcc met en garde à la -Wstrict-prototypesfois pour int func()et int main(): xc: 3: avertissement: la déclaration de fonction n'est pas un prototype. Vous devez déclarer main()comme main(void)aussi bien.
Jens
3
@Jens Pourquoi avez-vous modifié la question? Vous semblez avoir manqué le point ...
dlras2
1
Je viens d'expliciter l'int implicite. Comment cela manque-t-il le point? Je crois que le point est pourquoi int func();est compatible avec int func(arglist) { ... }.
Jens
3
@MatsPetersson C'est faux. C99 5.1.2.2.1 contredit expressément votre affirmation et déclare que la version sans argument l'est int main(void).
Jens

Réponses:

271

Toutes les autres réponses sont correctes, mais juste pour terminer

Une fonction est déclarée de la manière suivante:

  return-type function-name(parameter-list,...) { body... }

type de retour est le type de variable renvoyé par la fonction. Il ne peut pas s'agir d'un type de tableau ou d'un type de fonction. S'il n'est pas donné, alors int est supposé .

nom-fonction est le nom de la fonction.

liste-paramètres est la liste des paramètres que la fonction prend séparés par des virgules. Si aucun paramètre n'est donné, la fonction n'en prend aucun et doit être définie avec un jeu de parenthèses vide ou avec le mot clé void. Si aucun type de variable n'est devant une variable dans la liste des paramètres, alors int est supposé . Les tableaux et les fonctions ne sont pas transmis aux fonctions, mais sont automatiquement convertis en pointeurs. Si la liste se termine par des points de suspension (, ...), il n'y a pas de nombre défini de paramètres. Remarque: l'en-tête stdarg.h peut être utilisé pour accéder aux arguments lors de l'utilisation de points de suspension.

Et encore une fois par souci d'exhaustivité. De la spécification C11 6: 11: 6 (page: 179)

L' utilisation de déclarateurs de fonctions avec des parenthèses vides (pas des déclarateurs de type de paramètre au format prototype) est une fonction obsolète .

Krishnabhadra
la source
2
"Si aucun type de variable n'est devant une variable dans la liste des paramètres, alors int est supposé." Je vois cela dans le lien fourni par le vôtre, mais je ne le trouve dans aucune norme c89, c99 ... Pouvez-vous fournir une autre source?
godaygo
"Si aucun paramètre n'est donné, alors la fonction n'en prend aucun et doit être définie avec un jeu de parenthèses vide" semble contradictoire avec la réponse de Tony The Lion mais je viens d'apprendre que la réponse de Tony The Lion est correcte à la dure.
jakun
160

En C func()signifie que vous pouvez passer n'importe quel nombre d'arguments. Si vous ne voulez aucun argument, vous devez déclarer en tant que func(void). Le type que vous transmettez à votre fonction, s'il n'est pas spécifié par défaut, est int.

Tony le lion
la source
3
En fait, il n'y a pas de type unique par défaut à int. En fait, tous les arguments sont par défaut int (même le type de retour). Il est tout à fait correct d'appeler func(42,0x42);(où tous les appels doivent utiliser le formulaire à deux arguments).
Jens
1
En C, il est assez courant que les types non spécifiés prennent par défaut la valeur int comme par exemple lorsque vous déclarez une variable: unsigned x; De quel type est la variable x? Il s'avère que son int signé
bitek
4
Je dirais plutôt que l'int implicite était courant dans les temps anciens. Ce n'est certainement plus et en C99 il a été supprimé de C.
Jens
2
Il peut être utile de noter que cette réponse s'applique aux prototypes de fonctions avec des listes de paramètres vides, mais pas aux définitions de fonctions avec des listes de paramètres vides.
autiste
@mnemonicflow qui n'est pas un "type non spécifié par défaut à int"; unsignedest juste un autre nom pour le même type que unsigned int.
hobbs
58

int func();est une déclaration de fonction obsolète des jours où il n'y avait pas de norme C, c'est-à-dire les jours de K&R C (avant 1989, l'année de publication de la première norme "ANSI C").

N'oubliez pas qu'il n'y avait pas de prototypes dans K&R C et que le mot-clé voidn'était pas encore inventé. Tout ce que vous pouviez faire était d'indiquer au compilateur le type de retour d'une fonction. La liste de paramètres vide dans K&R C signifie "un nombre non spécifié mais fixe" d'arguments. Fixe signifie que vous devez appeler la fonction avec le même nombre d'arguments à chaque fois (par opposition à une fonction variadique commeprintf , où le nombre et le type peuvent varier pour chaque appel).

De nombreux compilateurs diagnostiqueront cette construction; en particulier gcc -Wstrict-prototypes, vous dira que "la déclaration de fonction n'est pas un prototype", ce qui est parfait, car il semble à un prototype (surtout si vous êtes empoisonné par C ++!), mais ce n'est pas le cas. Il s'agit d'une déclaration de type de retour K&R C à l'ancienne.

Règle générale: ne laissez jamais une déclaration de liste de paramètres vide vide, utilisez int func(void)pour être spécifique. Cela transforme la déclaration de type de retour K&R en un prototype C89 approprié. Les compilateurs sont contents, les développeurs sont contents, les vérificateurs statiques sont contents. Ceux qui sont trompés par ^ W ^ Wfond de C ++ peuvent grincer des dents, car ils doivent taper des caractères supplémentaires lorsqu'ils essaient d'exercer leurs compétences en langues étrangères :-)

Jens
la source
1
Veuillez ne pas relier cela à C ++. Il est logique que la liste d'arguments vide ne signifie aucun paramètre , et les gens du monde ne sont pas intéressés par les erreurs commises par les gars de K&R il y a environ 40 ans. Mais les experts du comité continuent de faire glisser des choix contre-nature - en C99, C11.
pfalcon
1
@pfalcon Le bon sens est assez subjectif. Ce qui est du bon sens pour une personne, c'est de la folie pour les autres. Les programmeurs C professionnels savent que les listes de paramètres vides indiquent un nombre d'arguments non spécifié mais fixe. Changer cela casserait probablement de nombreuses implémentations. Blâmer les comités d'avoir évité les changements silencieux revient à aboyer le mauvais arbre, à mon humble avis.
Jens
53
  • La liste de paramètres vide signifie "tous les arguments", donc la définition n'est pas fausse.
  • Le type manquant est supposé être int.

Je considérerais que toute construction qui passe cela manque dans le niveau d'avertissement / d'erreur configuré, il n'y a aucun intérêt à autoriser le code réel.

se détendre
la source
30

C'est la déclaration et la définition de la fonction de style K&R . De la norme C99 (ISO / IEC 9899: TC3)

Section 6.7.5.3 Déclarateurs de fonctions (y compris les prototypes)

Une liste d'identifiants ne déclare que les identifiants des paramètres de la fonction. Une liste vide dans un déclarant de fonction qui fait partie d'une définition de cette fonction spécifie que la fonction n'a pas de paramètres. La liste vide dans un déclarant de fonction qui ne fait pas partie d'une définition de cette fonction spécifie qu'aucune information sur le nombre ou les types de paramètres n'est fournie. (Si les deux types de fonctions sont "à l'ancienne", les types de paramètres ne sont pas comparés.)

Section 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 de paramètre au format prototype) est une fonctionnalité obsolète.

Section 6.11.7 Définitions des fonctions

L'utilisation de définitions de fonctions avec des identificateurs de paramètres et des listes de déclarations distincts (et non des déclarants de type de paramètres et de types de paramètres) est une fonction obsolète.

Ce qui signifie que l'ancien style K & R de style

Exemple:

Déclaration: int old_style();

Définition:

int old_style(a, b)
    int a; 
    int b;
{
     /* something to do */
}
Lei Mou
la source
16

C suppose que intsi aucun type n'est donné sur le type de retour de fonction et la liste des paramètres . Ce n'est que pour cette règle que des choses étranges sont possibles.

Une définition de fonction ressemble à ceci.

int func(int param) { /* body */}

Si c'est un prototype que vous écrivez

int func(int param);

Dans le prototype, vous ne pouvez spécifier que le type de paramètres. Le nom des paramètres n'est pas obligatoire. Donc

int func(int);

De plus, si vous ne spécifiez pas le type de paramètre mais que le nom intest supposé comme type.

int func(param);

Si vous allez plus loin, la suite fonctionne aussi.

func();

Le compilateur suppose int func()que vous écrivez func(). Mais ne mettez pas à l' func()intérieur d'un corps de fonction. Ce sera un appel de fonction

Shiplu Mokaddim
la source
3
Une liste de paramètres vide n'est pas liée au type int implicite. int func()n'est pas une forme implicite de int func(int).
John Bartholomew
11

Comme indiqué @Krishnabhadra, toutes les réponses précédentes des autres utilisateurs ont une interprétation correcte, et je veux juste faire une analyse plus détaillée de certains points.

Dans l'ancien C comme dans l'ANSI-C, le " paramètre formel non typé ", prenez la dimension de votre registre de travail ou capacité de profondeur d'instruction (registres fantômes ou cycle cumulatif d'instructions), dans une MPU 8 bits, sera un int16, dans une 16 bits MPU et ainsi sera un int16 et ainsi de suite, dans le cas où les architectures 64 bits peuvent choisir de compiler des options comme: -m32.

Bien qu'il semble plus simple de l'implémentation à haut niveau, Pour passer plusieurs paramètres, le travail du programmeur dans l'étape de type de données de contrôle dimencion, devient plus exigeant.

Dans d'autres cas, pour certaines architectures de microprocesseurs, les compilateurs ANSI personnalisés, ont exploité certaines de ces anciennes fonctionnalités pour optimiser l'utilisation du code, forçant l'emplacement de ces "paramètres formels non typés" à fonctionner dans ou en dehors du registre de travail, aujourd'hui vous obtenez presque la même chose avec l'utilisation de "volatile" et de "registre".

Mais il faut noter que les compilateurs les plus modernes, ne font aucune distinction entre les deux types de déclaration de paramètres.

Exemples de compilation avec gcc sous linux:

principal c

main2.c

main3.c  
Dans tous les cas, la déclaration du prototype localement ne sert à rien, car il n'y a pas d'appel sans paramètres, la référence à ce prototype sera négligente. Si vous utilisez le système avec "paramètre formel non typé", pour un appel externe, procédez à la génération d'un type de données prototype déclaratif.

Comme ça:

int myfunc(int param);
RTOSkit
la source
5
J'ai pris la peine d'optimiser les images pour que la taille en octets des images, soit le minimum possible. Je pense que les images peuvent obtenir un aperçu rapide de l'absence de différence dans le code généré. J'ai testé le code, généré une vraie documentation de ce qu'il prétendait et pas seulement théorisé sur le problème. mais tous ne voient pas le monde de la même manière. Je suis vraiment désolé que ma tentative de fournir plus d'informations à la communauté vous ait tant dérangé.
RTOSkit
1
Je suis à peu près certain qu'un type de retour non spécifié est toujours int, qui est généralement soit la taille du registre de travail, soit 16 bits, selon le plus petit.
supercat
@supercat +1 Déduction parfaite, vous avez raison! C'est exactement ce que je discute ci-dessus, un compilateur conçu par défaut pour une architecture spécifiée, reflète toujours la taille du registre de travail du CPU / MPU en question, donc dans un environnement embarqué, il existe différentes stratégies de re-typage sur un OS en temps réel, dans une couche de compilation (stdint.h), ou dans une couche de portabilité, qui rend portable le même OS dans de nombreuses autres architectures, et obtenu en alignant les types spécifiques CPU / MPU (int, long, long long, etc.) avec les types de systèmes génériques u8, u16, u32.
RTOSkit
@supercat Sans types de contrôle offerts par un système d'exploitation ou par un compilateur spécifique, vous devez avoir un peu d'attention dans le temps de développement et vous assurez que toutes les "affectations non typées" sont alignées avec la conception de votre application, avant d'avoir la surprise de trouver un " 16bit int "et non un" 32bit int ".
RTOSkit
@RTOSkit: Votre réponse suggère que le type par défaut sur un processeur 8 bits sera un Int8. Je me souviens de quelques compilateurs C-ish pour l'architecture de la marque PICmicro où c'était le cas, mais je ne pense pas que quelque chose ressemblant à distance à une norme C ait jamais permis à un inttype qui n'était pas capable à la fois de contenir toutes les valeurs de la plage - 32767 à +32767 (la note -32768 n'est pas requise) et peut également contenir toutes les charvaleurs (ce qui signifie que si charest 16 bits, il doit être signé ou intdoit être plus grand).
supercat
5

Concernant le type de paramètre, il y a déjà des réponses correctes ici, mais si vous voulez l'entendre du compilateur, vous pouvez essayer d'ajouter des indicateurs (les indicateurs sont presque toujours une bonne idée de toute façon).

compiler votre programme en utilisant gcc foo.c -WextraJ'obtiens:

foo.c: In function func’:
foo.c:5:5: warning: type of param defaults to int [-Wmissing-parameter-type]

Étrangement, -Wextran'attrape pas cela clang(il ne reconnaît pas -Wmissing-parameter-typepour une raison quelconque, peut-être pour les éléments historiques mentionnés ci-dessus) mais le -pedanticfait:

foo.c:5:10: warning: parameter 'param' was not declared, 
defaulting to type 'int' [-pedantic]
int func(param)
         ^
1 warning generated.

Et pour le problème de prototype, comme indiqué ci-dessus, se int func()réfère à des paramètres arbitraires à moins que vous ne le définissiez de manière exclusive comme int func(void)ce qui vous donnerait alors les erreurs comme prévu:

foo.c: In function func’:
foo.c:6:1: error: number of arguments doesnt match prototype
foo.c:3:5: error: prototype declaration
foo.c: In function main’:
foo.c:12:5: error: too many arguments to function func
foo.c:5:5: note: declared here

ou en clangtant que:

foo.c:5:5: error: conflicting types for 'func'
int func(param)
    ^
foo.c:3:5: note: previous declaration is here
int func(void);
    ^
foo.c:12:20: error: too many arguments to function call, expected 0, have 1
    int bla = func(10);
              ~~~~ ^~
foo.c:3:1: note: 'func' declared here
int func(void);
^
2 errors generated.
aucun
la source
3

Si la déclaration de fonction n'a pas de paramètres, c'est-à-dire vide, alors elle prend un nombre non spécifié d'arguments. Si vous voulez qu'il ne prenne aucun argument, changez-le en:

int func(void);
PP
la source
1
"Si le prototype de fonction n'a pas de paramètres" Ce n'est pas un prototype de fonction, juste une déclaration de fonction.
effeffe
@effeffe a corrigé cela. Je ne savais pas que cette question attirerait beaucoup d'attention;)
PP
3
Le "prototype de fonction" n'est-il pas simplement une expression alternative (peut-être ancienne) pour "déclaration de fonction"?
Giorgio
@Giorgio IMO, les deux sont corrects. "prototype de fonction" doit correspondre à "définition de fonction". Je pense probablement que l'effeffe signifiait une déclaration dans la question et ce que je voulais dire est dans ma réponse.
PP
@Giorgio no, la déclaration de fonction est faite par la valeur du type de retour, l'identifiant et une liste de paramètres facultative ; les prototypes de fonction sont une déclaration de fonction avec une liste de paramètres.
effeffe
0

C'est pourquoi je conseille généralement aux gens de compiler leur code avec:

cc -Wmissing-variable-declarations -Wstrict-variable-declarations -Wold-style-definition

Ces indicateurs renforcent deux ou trois choses:

  • -Déclarations de variables manquantes: Il est impossible de déclarer une fonction non statique sans obtenir un prototype au préalable. Cela rend plus probable qu'un prototype dans un fichier d'en-tête correspond à la définition réelle. Alternativement, il impose que vous ajoutiez le mot-clé statique aux fonctions qui n'ont pas besoin d'être visibles publiquement.
  • -Wstrict-variable-declarations: le prototype doit lister correctement les arguments.
  • -Wold-style-definition: La définition de la fonction elle-même doit également lister correctement les arguments.

Ces indicateurs sont également utilisés par défaut dans de nombreux projets Open Source. Par exemple, FreeBSD a ces drapeaux activés lors de la construction avec WARNS = 6 dans votre Makefile.

Ed Schouten
la source