73 votes

func() vs func(void) en c99

void func() En pratique, un paramètre vide signifie que tout argument est accepté.

void func(void) n'accepte aucun argument.

Mais dans la norme C99, je trouve de telles lignes :

6.7.5.3 Déclarateurs de fonctions (y compris les prototypes)
14 Une liste d'identifiants ne déclare que les identifiants des paramètres de la fonction. Une liste vide dans un déclarateur 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éclarateur 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 des paramètres n'est fournie.

conformément à la norme, func() et func(void) est le même ?

77voto

Antti Haapala Points 11542

TL;DR

Dans les déclarations,

void func1();     // obsolescent
void func2(void);

le comportement est tout à fait différent. La première déclare une fonction sans prototype - et elle peut prendre n'importe quel nombre d'arguments ! Alors que la seconde déclare une fonction avec un prototype, qui n'a pas de paramètres et n'accepte aucun argument.

Sur définitions

void func1() { }     // obsolescent

et

void func2(void) { }
  • Le premier déclare et définit une fonction func1 qui n'a pas de paramètres et pas de prototype

  • Ce dernier déclare et définit une fonction func2 avec un prototype qui n'a pas de paramètres.

Ces deux éléments se comportent de manière distincte en ce sens que, alors que le compilateur C doit afficher un message de diagnostic lors de l'appel d'une fonction prototypée avec un nombre d'arguments erroné. n'a pas besoin le faire lors de l'appel d'une fonction sans prototype.

C'est-à-dire que, compte tenu des définitions ci-dessus

func1(1, 2, 3); // need not produce a diagnostic message
func2(1, 2, 3); // must always produce a diagnostic message 
                // as it is a constraint violation

Cependant les deux sont illégaux dans les programmes strictement conformes, car il s'agit d'un comportement explicitement indéfini, conformément à la règle suivante  6.5.2.2p6 .

En outre, les parenthèses vides sont considérées comme une fonctionnalité obsolète :

L'utilisation de déclarateurs de fonctions avec des parenthèses vides (et non de déclarateurs de types de paramètres sous forme de prototypes) est une fonctionnalité obsolète.

et

L'utilisation de définitions de fonctions avec des listes séparées d'identifiants et de déclarations de paramètres (et non des déclarateurs de types et d'identifiants de paramètres au format prototype) est une fonctionnalité obsolète.

En détail

Il existe deux concepts connexes, mais distincts : les paramètres et les arguments.

  • Les arguments sont les valeurs passées dans la fonction.

  • Les paramètres sont les noms/variables de la fonction qui sont définis comme les valeurs des arguments lorsque la fonction entre en jeu.

Dans l'extrait suivant :

int foo(int n, char c) {
    ...
}

...

    foo(42, ch);

n et c sont des paramètres. 42 et ch sont des arguments.

L'extrait cité ne concerne que les paramètres d'une fonction, mais ne mentionne rien sur le prototype ou les arguments de la fonction.


Le site déclaration void func1() signifie que la fonction func1 peut être appelé avec un nombre quelconque de arguments c'est-à-dire qu'aucune information sur le nombre d'arguments n'est spécifiée (en tant que déclaration séparée, C99 spécifie ceci comme "fonction sans spécification de paramètre"), alors que la déclaration void func2(void) signifie que la fonction func2 n'accepte pas de arguments du tout.

Le guillemet dans votre question signifie que dans un définition des fonctions , void func1() et void func2(void) les deux signalent qu'il n'y a pas paramètres c'est-à-dire des noms de variables qui sont définis par les valeurs des arguments lorsque la fonction est saisie. Le site void func() {} s'oppose à void func(); le premier déclare que func ne prend en effet aucun paramètre, alors que cette dernière est une déclaration pour une fonction func pour laquelle ni paramètres ni leurs types sont spécifiés (une déclaration sans prototype).

Cependant, ils diffèrent encore sur le plan de la définition en ce sens que

  • La définition void func1() {} ne déclare pas de prototype, alors que void func2(void) {} le fait, car () n'est pas une liste de types de paramètres, alors que (void) est une liste de types de paramètres ( 6.7.5.3.10 ) :

    Le cas particulier d'un paramètre non nommé de type void comme seul élément de la liste indique que la fonction n'a pas de paramètres.

    et en outre 6.9.1.7

    Si le déclarateur inclut une liste de types de paramètres, la liste spécifie également les types de tous les paramètres par défaut. spécifie également les types de tous les paramètres ; un tel déclarateur sert également de prototype de fonction pour les appels ultérieurs à la même fonction dans la même unité de traduction. Si le déclarateur comprend une liste d'identifiants, les types des paramètres sont déclarés dans une liste de déclaration suivante. Dans les deux cas, le type de chaque paramètre est ajusté comme décrit au point 6.7.5.3 pour une liste de types de paramètres ; le type résultant doit être un type d'objet.

    Le déclarateur de la définition de fonction pour func1 fait pas contiennent un liste des types de paramètres et donc la fonction n'a pas de prototype.

  • void func1() { ... } peut toujours être appelé avec un nombre quelconque d'arguments, alors que c'est une erreur de compilation d'appeler void func2(void) { ... } avec des arguments quelconques (6.5.2.2) :

    Si l'expression qui désigne la fonction appelée a un type qui comprend un prototype le nombre d'arguments doit correspondre au nombre de paramètres. Chaque argument doit avoir un type tel que sa valeur puisse être attribuée à un objet ayant la version non qualifiée du type de son paramètre correspondant.

    (c'est moi qui souligne)

    Il s'agit d'un contrainte qui, selon la norme, indique qu'une implémentation conforme doit afficher au moins un message de diagnostic sur ce problème. Mais comme func1 n'a pas de prototype, une implémentation conforme n'est pas tenue de produire des diagnostics.


Toutefois, si le nombre d'arguments n'est pas égal au nombre de paramètres, la fonction le comportement est indéfini 6.5.2.2p6 :

Si l'expression qui dénote la fonction appelée a un type qui n'est pas ne pas inclure de prototype , [...] Si le nombre d'arguments n'est pas égal au nombre de paramètres, le comportement est indéfini.

Donc, en théorie, un compilateur C99 conforme est également autorisé à commettre une erreur ou à diagnostiquer un avertissement dans ce cas. StoryTeller a fourni des preuves que clang pourrait diagnostiquer ceci Cependant, mon GCC ne semble pas le faire (et cela pourrait aussi être nécessaire pour qu'il soit compatible avec certains vieux codes obscurs) :

void test() { }

void test2(void) { }

int main(void) {
    test(1, 2);
    test2(1, 2);
}

Lorsque le programme ci-dessus est compilé avec gcc -std=c99 test.c -Wall -Werror le résultat est :

test.c: In function ‘main’:
test.c:7:5: error: too many arguments to function ‘test2’
     test2(1, 2);
     ^~~~~
test.c:3:6: note: declared here
 void test2(void) { }
      ^~~~~

C'est-à-dire que les arguments ne sont pas du tout vérifiés par rapport aux paramètres d'une fonction dont la déclaration en définition n'est pas prototypée ( test ) alors que GCC considère comme une erreur de compilation le fait de spécifier des arguments à une fonction prototypée ( test2 ) ; toute mise en œuvre conforme doit diagnostiquer cela car il s'agit d'une violation de contrainte.

21voto

Mats Points 3039

La partie significative de la citation est mise en évidence en gras ci-dessous :

6.7.5.3 Déclarateurs de fonctions (y compris les prototypes) 14 Une liste d'identificateurs déclare uniquement les identificateurs des paramètres de la fonction. Une liste vide dans un déclarateur de fonction qui est 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éclarateur de fonction qui est 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.

Ainsi, lorsque la liste des paramètres est vide pour une fonction avec son corps, ils sont identiques. Mais il ne s'agit que de la déclaration d'une fonction.

void function1(); // No information about arguments
void function2(void); // Function with zero arguments

void function3() {
    // Zero arguments
}

void function4(void) {
    // Zero arguments
}

10voto

usr Points 13665

Selon la norme, func() et func(void) sont identiques ?

Non. func(void) dit que la fonction prend pas de aucun argument, alors que func() indique que la fonction prend un nombre non spécifié d'arguments. Les deux sont valides, mais le func() sont obsolètes et ne devraient pas être utilisés.

C'est un artefact du C pré-standard. C99 l'a marqué comme obsolète.

6.11.6 Déclarateurs de fonctions :

L'utilisation de déclarateurs de fonctions avec des parenthèses vides (et non de déclarateurs de types de paramètres sous forme de prototypes) est une fonctionnalité obsolète.

A partir de la C11, elle reste obsolète et n'a pas été retirée de la norme.

6voto

Grzegorz Szpetkowski Points 10225

La liste de paramètres vide à l'intérieur d'une définition de fonction signifie que celle-ci n'inclut pas de prototype et n'a pas de paramètres.

C11 §6.9.1/7 Définitions des fonctions (l'accentuation dans les guillemets en cours est la mienne)

Le déclarateur dans une définition de fonction spécifie le nom de la définie et les identifiants de ses paramètres. Si le le déclarateur inclut une liste de types de paramètres la liste précise également les types de tous les paramètres ; un tel déclarateur sert également de prototype de fonction pour les appels ultérieurs à la même fonction dans la même unité de traduction.

La question est posée :

conformément à la norme, func() et func(void) est le même ?

Non. La différence essentielle entre void func() et void func(void) réside dans leurs appels.

C11 §6.5.2.2/2 Appels de fonction (dans contraintes section) :

Si l'expression qui désigne la fonction appelée a un type qui comprend un prototype le nombre d'arguments doit correspondre au nombre de paramètres . Chaque argument doit avoir un type tel que sa valeur puisse être assignée à un objet avec la version non qualifiée du de son paramètre correspondant.

Remarquez que les paramètres ≠ les arguments. La fonction peut ne contenir aucun paramètre, mais elle peut avoir plusieurs arguments.

Comme une fonction définie avec des paramètres vides n'introduit pas de prototype, elle n'est pas vérifiée par rapport à ses appels, donc en théorie elle peut être fournie avec quoi que ce soit le nombre d'arguments.

Cependant, il s'agit techniquement d'un comportement indéfini pour appeler une telle fonction avec au moins un argument (voir l'article d'Antti Haapala commentaires ).

C11 §6.5.2.2/6 Appels de fonction (dans sémantique section) :

Si le nombre d'arguments n'est pas égal au nombre de paramètres, le comportement est indéfini.

La différence est donc subtile :

  • Lorsqu'une fonction est définie avec void il ne compilera pas si le nombre d'arguments ne correspond pas aux paramètres (ainsi que leurs types), en raison d'une violation de la constance (§6.5.2.2/2). Une telle situation nécessite un message de diagnostic de la part du compilateur conforme.
  • S'il est défini avec des paramètres vides, il mai ou ne peut pas compiler (il n'est pas nécessaire d'obtenir un message de diagnostic de la part d'un compilateur conforme), mais il est possible d'utiliser UB pour appelez cette fonction.

Exemple :

#include <stdio.h>

void func1(void) { puts("foo"); }
void func2()     { puts("foo"); }

int main(void)
{
    func1(1, 2); // constraint violation, it shouldn't compile
    func2(3, 4); // may or may not compile, UB when called
    return 0;
}

Notez que compilateur optimisant peut couper les arguments dans ce cas. Par exemple, voici comment Clang compile le code ci-dessus (à l'exclusion de func1 ) avec -01 sur x86-64 selon les conventions d'appel de l'ABI de SysV :

main:                                   # @main
        push    rax          ; align stack to the 16-byte boundary
        call    func2        ; call func2 (no arguments given)
        xor     eax, eax     ; set zero as return value
        pop     rcx          ; restore previous stack position (RSP)
        ret

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X