70 votes

Un code étrange qui se compile avec g++

Le code suivant se compile avec succès avec g++ 4.8.1 :

int main()
{
    int(*)();
}

Cela ressemble à une simple déclaration d'un pointeur vers une fonction :

int(*f)();

Il ne compile pas avec clang 3.4 et vc++ 2013.

S'agit-il d'un bug du compilateur ou d'une des zones d'ombre de la norme ?


Liste de morceaux de code étranges similaires qui compilent bien avec g++ 4.8.1 (mis à jour) :

  1. int(*)();

  2. int(*);

  3. int(*){};

  4. int(*());

Exemple concret avec ces étranges morceaux de code .

Mise à jour 1 : @Ali a ajouté quelques informations intéressantes dans les commentaires :

Les 4 cas donnent une erreur de compilation avec clang 3.5 trunk (202594) et compilent bien avec gcc 4.9 trunk (20140302). Le comportement est le même avec -std=c++98 -pedantic sauf pour int(*){}; ce qui est compréhensible ; les listes d'initialisation étendues ne sont disponibles qu'avec l'option -std=c++11 .

Mise à jour 2 : Comme @CantChooseUsernames noté dans sa réponse ils compilent toujours bien même avec l'initialisation et aucun assemblage n'est généré pour eux par g++ (ni avec ni sans initialisation) même sans aucune optimisation activée :

  1. int(*)() = 0;

  2. int(*) = 0;

  3. int(*){} = 0;

  4. int(*()) = 0;

Exemple concret avec initialisations .

Mise à jour 3 : J'ai été vraiment surpris de constater que int(*)() = "Hello, world!"; compile bien, aussi (alors que int(*p)() = "Hello, world!"; ne compile pas, bien sûr).

Mise à jour 4 : C'est fantastique mais int(*){} = Hello, world!; compile bien. Et le morceau de code suivant, extrêmement étrange, aussi : int(*){}() = -+*/%&|^~.,:!?$()[]{}; ( exemple concret ).

15voto

Vlad from Moscow Points 36219

Selon la norme C++ (p. 6 de la section 7 Déclarations)

6 Chaque init-déclarateur de la liste des init-déclarateurs contient exactement un déclarant-id qui est le nom déclaré par ce init-declarator et donc un des noms déclarés par la déclaration

Il s'agit donc simplement d'un bug du compilateur.

Le code valide pourrait ressembler à l'exemple suivant (à l'exception de la déclaration du pointeur de fonction que vous avez indiqué), mais je ne peux pas le compiler avec mon MS VC++ 2010.

int(*p){};

Il semble que le compilateur que vous utilisez pour vos tests autorise les déclarations sans déclarateur-id.

Prenez également en compte le paragraphe suivant de la section 8.1 Noms de type

1 Pour spécifier les conversions de type de manière explicite, et en tant qu'argument de la commande sizeof, alignof, new, ou typeid le nom d'un type doit être spécifié. Cela peut être fait avec un type-id, qui est syntaxiquement une déclaration pour une variable ou une fonction de ce type qui omet le nom du type. déclaration d'une variable ou d'une fonction de ce type qui omet le nom de l'entité.

7voto

iavr Points 4645

Je ne sais pas si cela peut aider, mais j'ai essayé ce qui suit (clang 3.3, g++ 4.8.1) :

using P = int(*)();
using Q = int*;
P; // warning only
Q; // warning only
int(*)(); // error (but only in clang)
int*;     // error
int(*p)(); // ok
int *q;    // ok

Par contre, tout se compile bien dans g++ 4.8.2 et 4.9.0. Je n'ai pas de clang 3.4, malheureusement.

Très approximativement Une déclaration [section 7 de l'ISO] se compose des éléments suivants, dans l'ordre :

  1. des spécificateurs de préfixe facultatifs (par exemple static , virtual )
  2. le type de base (par exemple const double , vector<int> )
  3. déclarateur (par exemple n , *p , a[7] , f(int) )
  4. des spécificateurs de fonction de suffixe facultatifs (par exemple const , noexcept )
  5. initialisateur ou corps de fonction optionnel (par exemple = {1,2,3} o { return 0; }

Maintenant, un Déclarateur se compose grossièrement d'un nom et éventuellement de quelques opérateurs de déclaration [iso 8/4].

Opérateurs de préfixes, par exemple :

  • * (pointeur)
  • *const (pointeur constant)
  • & (référence lvalue)
  • && (référence rvalue)
  • auto (type de retour de fonction, à la fin)

Les opérateurs Postfix, par exemple :

  • [] (tableau)
  • () (fonction)
  • -> (type de retour en fin de fonction)

Les opérateurs ci-dessus ont été conçus pour refléter leur utilisation dans les expressions. Les opérateurs postfixes se lient plus étroitement que les préfixes, et les parenthèses peuvent être utilisées pour changer leur ordre : int *f() est une fonction qui renvoie un pointeur vers int alors que int (*f)() est un pointeur vers une fonction retournant int .

Je me trompe peut-être, mais je pense que ces opérateurs ne peuvent pas figurer dans la déclaration sans le nom. Ainsi, lorsque nous écrivons int *q; entonces int est le type de base, et *q est le déclarateur composé de l'opérateur préfixe * suivi du nom q . Mais int *; ne peut pas apparaître seule.

D'autre part, lorsque nous définissons using Q = int*; alors la déclaration Q; est bien en soi car Q est le type de base. Bien sûr, comme nous ne déclarons rien, nous pouvons obtenir une erreur ou un avertissement en fonction des options du compilateur, mais il s'agit d'une erreur différente.

Ce qui précède n'est que ma compréhension. Ce que dit la norme (par exemple N3337) est [iso 8.3/1] :

Chaque déclarateur contient exactement un déclarant-id ; il nomme l'identifiant qui est déclaré. Un site unqualified-id survenant dans un déclarant-id est un simple identifiant sauf pour la déclaration de certaines fonctions spéciales (12.3 [ conversions définies par l'utilisateur ], 12.4 [destructeurs], 13.5 [opérateurs surchargés]) et pour la déclaration de spécialisations de modèles ou de spécialisations partielles (14.7).

(les notes entre crochets sont les miennes). Je comprends donc int(*)(); devrait être invalide et je ne peux pas dire pourquoi il a un comportement différent dans clang et dans différentes versions de g++.

6voto

Brandon Points 4211

Vous pouvez utiliser ceci : http://gcc.godbolt.org/ pour voir l'assemblage..

int main()
{
    int(*)() = 0;
    return 0;
}

Génère :

main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $0, %eax
    popq    %rbp
    ret

Ce qui est équivalent à : int main() {return 0;} Donc, même sans aucune optimisation, gcc ne génère pas d'assemblage pour cela Devrait-il donner un avertissement ou une erreur ? Je n'en ai aucune idée mais il ne se soucie pas ou ne fait rien pour le pointeur func non nommé.

Cependant :

int main()
{
    int (*p)() = 0;
    return 0;
}

Sans optimisation, cela va générer :

main:
    pushq   %rbp
    movq    %rsp, %rbp
    movq    $0, -8(%rbp)
    movl    $0, %eax
    popq    %rbp
    ret

qui alloue 8 octets sur la pile

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