¿El estándar de C considera que hay uno o dos tipos 'struct uperms_entry' en este encabezado?
¿Puede dar el capítulo y verso 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?struct uperms_entry
los tipos en ella?
#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:
Si hay dos tipos, ¿hay alguna manera de que GCC informe el problema?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 interna del código: me gustaría que se invirtiera el orden de las estructuras, pero no estoy seguro de si estoy siendo demasiado pedante.
Actualizar:
Claramente, la respuesta a la pregunta inicial es 'hay unastruct uperms_entry
'y por lo tanto las preguntas numeradas 1 y 2 son discutibles. Me alegro de haber comprobado antes de lanzar un siseo en una revisión de código.
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 tipos compatibles 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 estructura, unión o enumerados declarados en unidades de traducción separadas son compatibles si sus etiquetas y miembros cumplen con los siguientes requisitos: Si se declara una con una etiqueta, la otra se debe declarar con la misma etiqueta. Si ambos se completan en cualquier lugar dentro de sus respectivas unidades de traducción, entonces se aplican los siguientes requisitos adicionales: debe haber 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 serán declarados en el mismo orden. Para dos estructuras o uniones, los campos de bits correspondientes tendrán los mismos anchos. Para dos enumeraciones, los miembros correspondientes tendrán los mismos valores.
55) Dos tipos no necesitan ser idénticos para ser compatibles.
§6.7.2.1 Especificadores de estructura y unión¶8 La presencia de una estructura-declaración-lista en una estructura-o-unión-especificador declara un nuevo tipo, dentro de una unidad de traducción. La estructura-declaración-lista es una secuencia de declaraciones para los miembros de la estructura o unión. Si la lista de declaración de estructura no contiene ningún miembro nombrado, ya sea directamente o a través de una estructura anónima o una unión anónima, el comportamiento no está definido. El tipo está incompleto hasta inmediatamente después de la}
que termina la lista, y completa a partir de entonces.
¶4 Todas las declaraciones de estructura, unión o tipos enumerados que tienen el mismo alcance 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á incompleto129) hasta inmediatamente después de la llave de cierre de la lista que define el contenido, y completar a continuación.
¶5 Dos declaraciones de estructura, unión o tipos enumerados que están en diferentes ámbitos o usan diferentes etiquetas declaran tipos distintos. Cada declaración 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
optar { struct-declaration-list }
o
enum identifier
optar { enumerator-list }
o
enum identifier
optar { 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 declaración 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 un especificador de tipo de la forma
struct-or-union identifier
ocurre de manera diferente a una de las formas anteriores, y ninguna otra declaración del identificador como una etiqueta es visible, entonces declara una estructura incompleta o tipo de unión, y declara el identificador como la etiqueta de ese tipo.131)
¶9 Si un especificador de tipo de la forma
struct-or-union identifier
o
enum identifier
ocurre de manera diferente a una de las formas anteriores, y una declaración del identificador como una etiqueta es visible, luego especifica el mismo tipo que la otra declaración y no vuelve a declarar la etiqueta.
¶12 EJEMPLO 2 Para ilustrar el uso de la declaración previa de una etiqueta para especificar un par de estructuras de referencia mutua, 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 declaración D1 se referiría a ella, no a la etiqueta s2 declarada en D2. Para eliminar esta sensibilidad de contexto, la declaración.
struct s2;
Puede ser insertado por delante de D1. Esto declara una nueva etiqueta s2 en el ámbito interno; La declaración D2 completa la especificación del nuevo tipo.
129) Un tipo incompleto puede ser usado solo cuando el tamaño de un objeto de ese tipo no es necesario. No es necesario, por ejemplo, cuando se declara que un nombre typedef es 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 declaración de la que forma parte. Por supuesto, cuando la declaración es de un nombre typedef, las declaraciones subsiguientes pueden hacer uso de ese nombre typedef para declarar objetos que tengan la estructura, unión o tipo enumerado especificados.
131) Una construcción similar con enumeración no existe.
§6.7.3 calificadores de tipo¶10 Para que dos tipos calificados sean compatibles, ambos deberán tener la versión calificada idéntica de un tipo compatible; el orden de los calificadores de tipo dentro de una lista de especificadores o calificadores no afecta al tipo especificado.
La discusión en §6.7.6 está relacionada con punteros, matrices y declaradores de funciones y no afecta realmente a estructuras o uniones.
Era consciente del Ejemplo 2 cuando escribí la pregunta. Este es un pensamiento en voz alta acerca de lo que significa la información anterior.
Considera este ejemplo, que compila limpiamente:
#include <stdio.h>
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("p.y = %d, q.z = %d\n", p.y, q.z);
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 declaracion dep1
en la función sería una estructura del mismo tipo que la variable globalp0
. A pesar de que su nombre de tipo esstruct r1
, es de un tipo diferente (e incompatible) del tipo de la variable localp
.
La redefinición destruct r1
a nivel global no está permitido, independientemente de si el elemento tiene nombrex
oy
. El anteriorstruct r1;
es un no-op en este contexto.
Un tema interesante es 'puede funcionarz
pasarp
oq
a cualquier otra función (llámaloa
)? La respuesta es un 'sí' calificado, y algunas de las restricciones son interesantes. (También sería un estilo de codificación atroz intentarlo, al borde de lo insano.) La función debe existir en una unidad de traducción (TU) separada. La declaración de función debe estar dentro de la función.z
(porque si está fuera de la función, su prototipo debe referirse a lastruct r1
definida fuera de la función, no lastruct r1
definido en el interior.
En la otra TU, debe prevalecer un grado de cordura: la funcióna
debe tener los tipos de estructura compatiblesstruct r1
ystruct r2
Visible en su ámbito global.
Aquí hay otro ejemplo, pero este no se compila:
#include <stdio.h>
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("p.y = %d, q.z = %d\n", p.y, q.z);
}
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("p.y = %d, q.z = %d\n", p.y, q.z);
}
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]
Las líneas 13 son la asignación.p.rn = &q;
en funcióny
Y la línea 23 es el intento de definir e inicializar.struct r2 p
en funciónz
.
Esto demuestra que dentro de las funciones, elrn
elemento destruct r2
Es un puntero al tipo incompleto.struct r1
declarado en el ámbito global. Añadiendo unstruct r1;
como la primera línea de código dentro de la función permitiría compilar el código, pero la referencia de inicializaciónr1p->rn
está volviendo a ser un puntero a un tipo incompleto de nuevo (el tipo incompleto es elstruct r1
declarado en el ámbito global).
Las declaraciones de funciones y las anteriores.struct r1;
La línea podría aparecer en un encabezado como un tipo opaco. La lista de funciones de apoyo es incompleta; tendría que haber una manera de obtener un puntero a un inicializadostruct r1
Pasar a las funciones, pero eso es un detalle.
Para hacer que el código funcione en esta segunda TU, los tipos destruct r1
debe estar completo en el alcance global antes de que se definan las funciones, y debido a las referencias recursivas, `struct r21 también debe estar completo.
#include <stdio.h>
/* 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("p.y = %d, q.z = %d\n", p.y, q.z);
}
void z(struct r1 *r1p)
{
struct r2 p = { r1p, 1 };
struct r1 q = { r1p->rn, 2 };
p.rn = &q;
printf("p.y = %d, q.z = %d\n", p.y, q.z);
}
Este proceso de definir las estructuras en el archivo de implementación mientras deja el tipo incompleto en el archivo de encabezado público se puede repetir en varios archivos de implementación si es necesario, aunque si más de una TU usa la definición de la estructura completa, sería mejor ubicar las definiciones en un archivo de encabezado 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 todo esto ya era obvio para ti. No había necesitado pensarlo en este nivel de detalle antes.