¿El estándar C considera que hay uno o dos tipos de ‘struct uperms_entry’ en este encabezado?

¿Puede dar capítulo y versículo de uno de los tres estándares de C (preferiblemente C99 o C11) que indica si el siguiente archivo de encabezado tiene uno o dos tipos de struct uperms_entry en él?

 #ifndef UPERMS_CACHE_INCLUDE #define UPERMS_CACHE_INCLUDE typedef struct mutex MT_MUTEX; typedef struct uperms_cache { MT_MUTEX *cache_lock; int processing; struct uperms_entry *uperms_list; // No prior struct uperms_entry } uperms_cache_t; typedef struct uperms_entry // Does this define a different struct uperms_entry? { char username[32]; int perms; struct uperms_entry *next; } uperms_entry_t; #endif /* UPERMS_CACHE_INCLUDE */ 

Preguntas adjuntas:

  1. Si hay dos tipos, ¿hay alguna forma de que GCC informe el problema?
  2. Si hay dos tipos, ¿alguna vez importa en la práctica?

(Creo que las respuestas son ‘sí, estrictamente hay dos tipos’, y luego (1) No y (2) No.)

Contexto: revisión del código interno: me gustaría que se revierte el orden de las estructuras, pero no estoy seguro de si estoy siendo excesivamente pedante.

Actualizar:

Claramente, la respuesta a la pregunta inicial es ‘hay una struct uperms_entry ‘ y por lo tanto las preguntas numeradas 1 y 2 son discutibles. Me alegro de haber verificado antes de lanzar un ataque de furia en una revisión del código.

Pensamiento de fondo

Esta sección se agregó mucho después de que se resolvió la pregunta principal.


Aquí hay algunas citas extensas pero relevantes de ISO / IEC 9899: 2011:

§6.2.7 Tipo compatible y tipo compuesto

¶1 Dos tipos tienen un tipo compatible si sus tipos son iguales. Las reglas adicionales para determinar si dos tipos son compatibles se describen en 6.7.2 para especificadores de tipo, en 6.7.3 para calificadores de tipo y en 6.7.6 para declaradores. 55) Además, dos tipos de estructuras, uniones o enumerados declarados en unidades de traducción separadas son compatibles si sus tags y miembros cumplen los siguientes requisitos: Si uno se declara con una etiqueta, el otro se declarará con la misma etiqueta. Si ambos se completan en cualquier lugar dentro de sus respectivas unidades de traducción, se aplicarán los siguientes requisitos adicionales: habrá una correspondencia uno a uno entre sus miembros, de modo que cada par de miembros correspondientes se declare con tipos compatibles; si un miembro del par se declara con un especificador de alineación, el otro se declara con un especificador de alineación equivalente; y si un miembro del par se declara con un nombre, el otro se declara con el mismo nombre. Para dos estructuras, los miembros correspondientes se declararán en el mismo orden. Para dos estructuras o uniones, los campos de bits correspondientes deben tener el mismo ancho. Para dos enumeraciones, los miembros correspondientes tendrán los mismos valores.

55) No es necesario que dos tipos sean idénticos para ser compatibles.

§6.7.2.1 Estructura y especificadores de unión

¶8 La presencia de una struct-declaration-list en un struct-o-union-specifier declara un nuevo tipo, dentro de una unidad de traducción. La struct-declaration-list es una secuencia de declaraciones para los miembros de la estructura o unión. Si struct-declaration-list no contiene ningún miembro con nombre, ya sea directamente o a través de una estructura anónima o unión anónima, el comportamiento no está definido. El tipo está incompleto hasta inmediatamente después de } que termina la lista y completa a partir de entonces.

§6.7.2.3 Etiquetas

¶4 Todas las declaraciones de estructura, unión o tipos enumerados que tienen el mismo scope y usan la misma etiqueta declaran el mismo tipo. Independientemente de si hay una etiqueta o qué otras declaraciones del tipo están en la misma unidad de traducción, el tipo está incompleto 129) hasta inmediatamente después de la llave de cierre de la lista que define el contenido, y se completa a partir de entonces.

¶5 Dos declaraciones de estructura, unión o tipos enumerados que se encuentran en diferentes ámbitos o usan tags diferentes declaran tipos distintos. Cada statement de una estructura, unión o tipo enumerado que no incluye una etiqueta declara un tipo distinto.

¶6 Un tipo de especificador de la forma

struct-or-union identifier opt { struct-declaration-list }

o

enum identifier opt { enumerator-list }

o

enum identifier opt { enumerator-list , }

declara una estructura, unión o tipo enumerado. La lista define el contenido de la estructura, el contenido de la unión o el contenido de la enumeración. Si se proporciona un identificador, 130) el especificador de tipo también declara que el identificador es la etiqueta de ese tipo.

¶7 Una statement de la forma

 struct-or-union identifier ; 

especifica una estructura o tipo de unión y declara el identificador como una etiqueta de ese tipo. 131)

¶8 Si es un especificador de tipo de la forma

 struct-or-union identifier 

ocurre de otra forma que no sea parte de uno de los formularios anteriores, y no hay otra statement del identificador como etiqueta visible, luego declara una estructura incompleta o tipo de unión, y declara el identificador como la etiqueta de ese tipo. 131)

¶9 Si es un especificador de tipo de la forma

 struct-or-union identifier 

o

 enum identifier 

ocurre de otra forma que no sea parte de uno de los formularios anteriores, y una statement del identificador como una etiqueta es visible, luego especifica el mismo tipo que esa otra statement y no redeclara la etiqueta.

¶12 EJEMPLO 2 Para ilustrar el uso de la statement previa de una etiqueta para especificar un par de estructuras mutuamente referenciales, las declaraciones

 struct s1 { struct s2 *s2p; /* ... */ }; // D1 struct s2 { struct s1 *s1p; /* ... */ }; // D2 

especifique un par de estructuras que contengan punteros entre sí. Sin embargo, tenga en cuenta que si s2 ya se hubiera declarado como una etiqueta en un ámbito adjunto, la statement D1 se referiría a ella, no a la etiqueta s2 declarada en D2. Para eliminar esta sensibilidad al contexto, la statement

 struct s2; 

puede insertarse antes que D1. Esto declara una nueva etiqueta s2 en el scope interno; la statement D2 luego completa la especificación del nuevo tipo.

129) Un tipo incompleto solo puede usarse cuando no se necesita el tamaño de un objeto de ese tipo. No es necesario, por ejemplo, cuando un nombre typedef se declara como un especificador para una estructura o unión, o cuando se declara un puntero o una función que devuelve una estructura o unión. (Consulte los tipos incompletos en 6.2.5.) La especificación debe estar completa antes de que se llame o defina dicha función.

130) Si no hay un identificador, el tipo puede, dentro de la unidad de traducción, ser referido solo por la statement de la cual es parte. Por supuesto, cuando la statement es de un nombre typedef, las declaraciones posteriores pueden hacer uso de ese nombre typedef para declarar objetos que tienen la estructura especificada, unión o tipo enumerado.

131) Una construcción similar con enum no existe.

§6.7.3 Calificadores de tipo

¶10 Para que dos tipos calificados sean compatibles, ambos tendrán la versión idénticamente calificada de un tipo compatible; el orden de los calificadores de tipo dentro de una lista de especificadores o calificadores no afecta el tipo especificado.

La discusión en §6.7.6 se relaciona con apuntadores, arreglos y declaradores de funciones y realmente no afecta estructuras o uniones.


Conocí el Ejemplo 2 cuando escribí la pregunta. Esto es un poco de pensar en voz alta acerca de lo que significa la información anterior.

Considere este ejemplo, que comstack limpiamente:

 #include  struct r1 { int x; }; struct r1; struct r1 p0; //struct r1 { int y; }; // Redefinition of struct r1 extern void z(void); void z(void) { struct r1 p1 = { 23 }; struct r1; //struct r1 p2; // Storage size of p2 is not known struct r2 { struct r1 *rn; int y; }; struct r1 { struct r2 *rn; int z; }; struct r2 p = { 0, 1 }; struct r1 q = { &p, 2 }; p.rn = &q; printf("py = %d, qz = %d\n", py, qz); printf("p1.x = %d\n", p1.x); } 

La función ilustra cuándo se aplica el ejemplo 2, pero no es un código sensible. La statement de p1 en la función sería una estructura del mismo tipo que la variable global p0 . Aunque su nombre de tipo es struct r1 , es de un tipo diferente (e incompatible) del tipo de la variable local p .

La redefinición de struct r1 a nivel global no está permitida, independientemente de si el elemento se llama x o y . La struct r1; anterior struct r1; es un no-op en este contexto.

Un aspecto interesante es ‘¿puede funcionar z pasar p o q a cualquier otra función (llamarlo a )? La respuesta es un ‘sí’ calificado, y algunas de las restricciones son interesantes. (También sería un estilo de encoding espantoso intentarlo, rayando en lo insano). La función debe existir en una unidad de traducción separada (TU). La statement de la función debe estar dentro de la función z (porque si está fuera de la función, su prototipo debe hacer referencia a la struct r1 definida fuera de la función, no a la struct r1 definida en el interior.

En la otra TU, debe prevalecer un grado de cordura: la función a debe tener los tipos de estructura compatibles struct r1 y struct r2 visibles en su scope global.

Aquí hay otro ejemplo, pero este no comstack:

 #include  struct r1; extern void z(struct r1 *r1p); extern void y(struct r1 *r1p); void y(struct r1 *r1p) { struct r2 { struct r1 *rn; int y; }; struct r1 { struct r2 *rn; int z; }; struct r2 p = { r1p, 1 }; struct r1 q = { &p, 2 }; p.rn = &q; printf("py = %d, qz = %d\n", py, qz); } void z(struct r1 *r1p) { struct r1 struct r2 { struct r1 *rn; int y; }; struct r1 { struct r2 *rn; int z; }; struct r2 p = { r1p, 1 }; struct r1 q = { &p, 2 }; p.rn = &q; printf("py = %d, qz = %d\n", py, qz); } 

Las advertencias de GCC 4.7.1 en Mac OS X 10.7.4 son:

 structs3.c: In function 'y': structs3.c:13:10: warning: assignment from incompatible pointer type [enabled by default] structs3.c: In function 'z': structs3.c:22:12: warning: initialization from incompatible pointer type [enabled by default] structs3.c:22:12: warning: (near initialization for 'p.rn') [enabled by default] 

La línea 13 es la asignación p.rn = &q; en la función y y la línea 23 es el bash de definir e inicializar la struct r2 p en la función z .

Esto demuestra que dentro de las funciones, el elemento rn de struct r2 es un puntero al tipo incompleto struct r1 declarado en el scope global. Agregar una struct r1; ya que la primera línea de código dentro de la función permitiría la comstackción del código, pero la inicialización que hace referencia a r1p->rn libera de nuevo un puntero a un tipo incompleto (el tipo incompleto es la struct r1 declarada en el scope global).

Las declaraciones de funciones y la struct r1; anterior struct r1; línea podría aparecer en un encabezado como un tipo opaco. La lista de funciones de apoyo está incompleta; debería haber una manera de hacer que un puntero a una struct r1 inicializada pase a las funciones, pero eso es un detalle.

Para hacer que el código funcione en esta segunda TU, los tipos para struct r1 deben estar completos en el scope global antes de que se definan las funciones, y debido a las referencias recursivas, `struct r21 también debe estar completo.

 #include  /* Logically in a 3-line header file */ struct r1; extern void z(struct r1 *r1p); extern void y(struct r1 *r1p); /* Details private to this TU */ struct r2 { struct r1 *rn; int y; }; struct r1 { struct r2 *rn; int z; }; void y(struct r1 *r1p) { struct r2 p = { r1p, 1 }; struct r1 q = { r1p->rn, 2 }; p.rn = &q; printf("py = %d, qz = %d\n", py, qz); } void z(struct r1 *r1p) { struct r2 p = { r1p, 1 }; struct r1 q = { r1p->rn, 2 }; p.rn = &q; printf("py = %d, qz = %d\n", py, qz); } 

Este proceso de definición de las estructuras en el archivo de implementación mientras deja el tipo incompleto en el archivo de cabecera público puede repetirse en múltiples archivos de implementación si es necesario, aunque si más de una TU usa la definición de estructura completa, sería mejor colocar las definiciones en un archivo de cabecera privado compartido solo entre los archivos que implementan las estructuras. Observo que no importa si el encabezado privado precede o sigue al encabezado público.

Tal vez esto ya era obvio para ti. No habría necesitado pensar en este nivel de detalle antes.

En C 1 , se refieren al mismo tipo. C99 §6.2.1 define los ámbitos que existen:

2 Para cada entidad diferente que designa un identificador, el identificador es visible (es decir, se puede usar) solo dentro de una región del texto del progtwig llamada su scope . Las diferentes entidades designadas por el mismo identificador tienen diferentes ámbitos o están en espacios de nombres diferentes. Hay cuatro tipos de ámbitos: función, archivo, bloque y prototipo de función. (Un prototipo de función es una statement de una función que declara los tipos de sus parámetros).

El scope de la función solo se aplica a las tags (como se indica explícitamente más adelante en la misma sección). El scope de bloque se aplica a los identificadores declarados en bloques : los bloques se crean mediante sentencias compuestas, iteración-sentencias y sentencias de selección (y no mediante declaraciones de struct o inicializadores compuestos). El scope del prototipo de función se aplica a los identificadores declarados dentro de una statement de prototipo de función.

Ninguno de estos se aplica a su ejemplo: todas las menciones de struct uperms_entry en su ejemplo se encuentran en el scope del archivo.

C99 §6.7.2.3 dice:

1 Todas las declaraciones de estructura, unión o tipos enumerados que tienen el mismo scope y usan la misma etiqueta declaran el mismo tipo. El tipo está incompleto hasta el corchete de cierre de la lista que define el contenido y se completa a partir de entonces.

Esto es bastante claro y se aplica a su caso.

El párrafo 8 de esa sección se aplica a la primera mención de struct uperms_entry :

8 Si se produce un especificador de tipo del identificador struct-o-union del formulario que no sea parte de uno de los formularios anteriores, y no hay otra statement del identificador como etiqueta visible, entonces declara una estructura incompleta o un tipo de unión, y declara el identificador como la etiqueta de ese tipo.

Entonces, en ese momento, se declara como un tipo incompleto en el scope del archivo. El párrafo 6 se aplica a la segunda mención de struct uperms_entry :

6 Un especificador de tipo del formulario identificador struct-o-union opt {struct-declaration-list} o enum identifier {enumerator-list} o enum identifier {enumerator-list,} declara una estructura, unión o tipo enumerado. La lista define el contenido de la estructura, el contenido de la unión o el contenido de la enumeración. Si se proporciona un identificador, el especificador de tipo también declara que el identificador es la etiqueta de ese tipo.

Entonces, después de la } al final de esa statement typedef, ahora es un tipo completo.

Las preguntas adjuntas son discutibles.


1. Creo que este no es el caso en C ++, sin embargo.

Bueno, C99 6.2.5.22 dice

… Un tipo de estructura o unión de contenido desconocido (como se describe en 6.7.2.3) es un tipo incompleto. Se completa, para todas las declaraciones de ese tipo, al declarar la misma estructura o etiqueta de unión con su contenido de definición más adelante en el mismo ámbito.

Lo que significaría que son del mismo tipo para su caso.