struct Figlio { char nome[256]; struct Genitore* padre; struct Genitore* madre; }; struct Genitore { char nome[256]; struct Figlio* progenie; }; int main() { return 0; } Esperaba que el código anterior no se compilara ya que struct Genitore no se declara antes de Figlio , por lo que se necesitaba una declaración directa de Genitore (es decir, struct Genitore; en la parte superior). En cambio, en C, compila sin ningún problema.
En cambio, si paso un puntero de estructura aún no declarado a una función, por ejemplo,
void f(struct st*); struct st{};lanza la siguiente advertencia
warning: 'struct st' declared inside parameter list will not be visible outside of this definition or declaration Entonces, no entiendo por qué estos comportamientos diferentes y, especialmente, por qué reenviar declaraciones de struct Genitore; y struct st; respectivamente no se solicitan.
Esta es una cuestión de alcance.
Primero, para la regla con respecto a una etiqueta de struct , la sección 6.2.1p7 del estándar C establece:
Las etiquetas de estructura, unión y enumeración tienen un ámbito que comienza justo después de la aparición de la etiqueta en un especificador de tipo que declara la etiqueta. ...
Que es lo que permite que la apariencia de una etiqueta de struct actúe como una declaración dondequiera que aparezca. Esto también permite que una estructura contenga un puntero a sí misma y haga que se refiera al mismo tipo.
Luego, las reglas para el alcance de los identificadores se enumeran en la sección 6.2.1p4:
Todos los demás identificadores tienen un alcance determinado por la ubicación de su declaración (en un declarador o especificador de tipo). Si el declarador o especificador de tipo que declara el identificador aparece fuera de cualquier bloque o lista de parámetros, el identificador tiene alcance de archivo , que termina al final de la unidad de traducción. Si el declarador o especificador de tipo que declara el identificador aparece dentro de un bloque o dentro de la lista de declaraciones de parámetros en una definición de función, el identificador tiene alcance de bloque , que termina al final del bloque asociado. Si el declarador o especificador de tipo que declara el identificador aparece dentro de la lista de declaraciones de parámetros en un prototipo de función (que no forma parte de una definición de función), el identificador tiene un alcance de prototipo de función , que termina al final del declarador de función. Si un identificador designa dos entidades diferentes en el mismo espacio de nombres, los ámbitos pueden superponerse. Si es así, el alcance de una entidad (el alcance interno) terminará estrictamente antes que el alcance de la otra entidad (el alcance externo). Dentro del ámbito interior, el identificador designa la entidad declarada en el ámbito interior; la entidad declarada en el ámbito externo está oculta (y no es visible) dentro del ámbito interno
Tenga en cuenta en particular los dos pasajes en negrita. La declaración implícita de struct Genitore al declarar un puntero a ese tipo como miembro de struct Figlio tiene alcance de archivo, ya que no aparece en una declaración de bloque y no aparece en una declaración de función, y por lo tanto es visible desde ese punto hacia abajo en el archivo fuente.
Por el contrario, la declaración implícita que aparece en la declaración de la función solo está dentro del alcance de la declaración misma. Tal declaración de tipo es problemática porque cualquier puntero de estructura potencial que pase se referirá a un tipo diferente al declarado implícitamente en la declaración de función.
En esta declaración
struct Figlio { char nome[256]; struct Genitore* padre; struct Genitore* madre; }; declaraste dos punteros a la estructura de tipo incompleta struct Genitore .
Un tipo de puntero es un tipo de objeto completo.
Es decir, en estas declaraciones declaró el tipo incompleto struct Genitore y objetos del tipo puntero struct Genitore * .
La declaración del tipo de estructura struct Genitore es visible en el ámbito donde se declara la estructura Figlio (en un ámbito de archivo o un ámbito de bloque)
Otro ejemplo de introducción de un tipo de estructura incompleto es una declaración en una declaración typedef, por ejemplo
typedef struct AA; .
Puede definir la estructura en algún lugar debajo después de la declaración typedef en el mismo ámbito.
Otro ejemplo es declarar una estructura de tipo incompleto en el tipo de retorno de una declaración de función. Por ejemplo
struct A f( void );Nuevamente, deberá definir la estructura al menos antes de la definición de la función o antes de una llamada a la función.
Aquí hay un programa de demostración.
#include <stdio.h> struct A f( void ); struct A { int x; } f( void ) { struct A a = { .x = 10 }; return a; }; int main( void ) { struct A a = f(); printf( "ax = %d\n", ax ); }La salida del programa es
ax = 10Por otro lado, si intenta eliminar la referencia de un puntero a un tipo incompleto, el compilador generará un error.
En esta declaración
void f(struct st*); el especificador de tipo struct st tiene el alcance de prototipo de función y no es visible fuera de la lista de parámetros de función.
Del Estándar C (6.2.1 Alcances de los identificadores)
- ...Si el declarador o especificador de tipo que declara el identificador aparece dentro de la lista de declaraciones de parámetros en un prototipo de función (que no forma parte de una definición de función), el identificador tiene un alcance de prototipo de función, que termina al final del declarador de función.