Tous les identificateurs de structure sont automatiquement déclarés

Tandis que answer warning: affectation à partir d’un type de pointeur incompatible pour le tableau linklist , j’ai remarqué que tout identifiant non déclaré perced avec le mot clé struct est considéré comme un identifiant déclaré.

Par exemple, le programme ci-dessous comstack bien:

 /* Comstack with "gcc -std=c99 -W -Wall -O2 -pedantic %" */ #include  struct foo { struct bar *next; /* Linked list */ }; int main(void) { struct bar *a = 0; struct baz *b = 0; struct foo c = {0}; printf("bar -> %p\n", (void *)a); printf("baz -> %p\n", (void *)b); printf("foo -> %p, %zu\n", (void *)&c, sizeof c); /* Remove %zu if compiling with -ansi flag */ return 0; } 

Ma question: quelle règle guide un compilateur C pour traiter les struct identifier non déclarés en tant que types de struct incomplets déclarés en struct ?

La norme dit ( 6.2.5.28 )

Tous les pointeurs sur les types de structure doivent avoir les mêmes exigences de représentation et d’alignement.

Cela signifie que le compilateur sait comment représenter les pointeurs sur n’importe quelle structure, même celles qui sont (encore) indéfinies.
Votre programme ne traite que des pointeurs vers de telles structures , alors ça va.

Il est décrit dans 6.2.5 Types et 6.7.2.3 Balises.

struct identifier est un type d’object.

6.2.5 Types

  1. La signification d’une valeur stockée dans un object ou renvoyée par une fonction est déterminée par le type de l’expression utilisée pour y accéder. (Un identifiant déclaré comme étant un object est l’expression la plus simple de ce type; le type est spécifié dans la déclaration de l’identifiant.) Les types sont partitionnés en types d’object (types décrivant des objects) et types de fonction (types décrivant des fonctions). À différents points de l’unité de traduction, un type d’object peut être incomplet (informations insuffisantes pour déterminer la taille des objects de ce type) ou complet (informations suffisantes). 37)

37) Un type peut être incomplet ou complet dans l’ensemble d’une unité de traduction, ou il peut changer d’états à différents endroits de l’unité de traduction.

  1. Un type de tableau de taille inconnue est un type incomplet. Il est complété, pour un identifiant de ce type, en spécifiant la taille dans une déclaration ultérieure (avec un lien interne ou externe). Un type de structure ou d’union de contenu inconnu (comme décrit au 6.7.2.3) est un type incomplet. Pour toutes les déclarations de ce type, elle est terminée en déclarant la même structure ou la même balise d’union avec son contenu définissant ultérieurement dans la même scope.

6.7.2.3 Tags

  1. Toutes les déclarations de types structure, d’union ou énumérées ayant la même scope et utilisant la même balise déclarent le même type. Indépendamment du fait qu’il y ait une balise ou quelles autres déclarations du type se trouvent dans la même unité de traduction, le type est incomplet jusqu’à ce qu’il soit immédiatement après l’accolade de fermeture de la liste définissant le contenu, et se termine ensuite.

129) Un type incomplet ne peut être utilisé que lorsque la taille d’un object de ce type n’est pas nécessaire. Cela n’est pas nécessaire, par exemple, lorsqu’un nom de typedef est déclaré comme spécificateur d’une structure ou d’une union, ou lorsqu’un pointeur sur une fonction ou une fonction renvoyant une structure ou une union est en cours de déclaration. (Voir Types incomplets dans 6.2.5.) La spécification doit être complète avant qu’une telle fonction ne soit appelée ou définie.

En plus de la réponse fournie par 2501 et de votre commentaire, ” Dans mon cas, il n’existe même pas de déclaration “, ce qui suit.

Toute utilisation d’une struct tag compte comme une déclaration (en aval) du type de structure, si elle n’avait pas été déclarée auparavant. Bien qu’une manière plus formelle consisterait à dire qu’il s’agit simplement d’un type, puisque la norme C ne mentionne pas les “déclarations en aval de types de structure”, mais simplement les types de structure complets et incomplets (6.2.5p22) .

6.7.2 Les spécificateurs de type nous indiquent qu’un spécificateur struct-or-union est un spécificateur de type , et 6.7.2.1 Spécificateurs de structure et d’union Le paragraphe 1 nous indique que l’ identificateur de structure est à son tour un spécificateur struct-ou-union .

Supposons que vous ayez une déclaration de liste chaînée, quelque chose comme

 struct node { struct node *next; int element; }; 

alors la “déclaration aval implicite” de ce type incomplet est essentielle au fonctionnement de cette structure. Après tout, le type struct node n’est terminé qu’au point-virgule de fin. Mais vous devez vous y référer afin de déclarer le pointeur next .

En outre, une déclaration de struct node (de type incomplet) peut sortir de la scope, comme toute autre déclaration. Cela se produit par exemple si vous avez un prototype

 int function(struct unknown *parameter); 

où la struct unknown sort du champ immédiatement à la fin de la déclaration. Toute autre struct unknown déclarée n’est alors pas la même que celle-ci. Cela est impliqué dans le texte du 6.2.5p22 :

Un type de structure ou d’union de contenu inconnu (comme décrit au 6.7.2.3) est un type incomplet. Pour toutes les déclarations de ce type, elle est terminée en déclarant la même structure ou la même balise d’union avec son contenu définissant ultérieurement dans la même scope .

C’est pourquoi le gcc met en garde à ce sujet:

 foo.c:1:21: warning: 'struct unknown' declared inside parameter list foo.c:1:21: warning: its scope is only this definition or declaration, which is probably not what you want 

Vous pouvez résoudre ce problème en plaçant une déclaration forward supplémentaire devant celle-ci, ce qui fait que la scope commence plus tôt (et se termine donc plus tard):

 struct unknown; int function(struct unknown *parameter); 

Je pense que le cas d’utilisation le plus élégant dans lequel des types de structure incomplets sont utilisés ressemble à ceci:

 struct foo { struct bar *left; struct bar *right; }; struct bar { int something; struct foo *next; }; 

Ie double récursion, où a pointe vers b et b pointe vers a. De tels cas peuvent être une raison pour laquelle cette fonctionnalité a été incluse dans la spécification originale du langage C.

La question initiale est de savoir si tous les identificateurs de structure sont automatiquement déclarés . Je pense qu’il vaudrait mieux dire que toutes les définitions de structure incomplètes sont automatiquement considérées comme des déclarations en aval .

Edit: Après le commentaire sur la documentation, examinons la bible du langage C: Kerninghan & Ritchie – Le langage de programmation C, section “6.5 Structures autoréférentielles” dit:

De temps en temps, il faut une variante de structures auto-référentielles: deux structures qui se réfèrent l’une à l’autre. La façon de gérer cela est:

 struct t { ... struct s *p; /* p points to an s */ }; struct s { ... struct t *q; /* q points to at */ }; 

Je conviens qu’il est possible d’implémenter une autre méthode, mais je considérerais cela comme une bonne motivation des auteurs du langage C et je suis d’accord avec eux pour dire que c’est une manière élégante de l’implémenter.