Advertencia de comstackdor extraño C: advertencia: ‘struct’ declarado dentro de la lista de parámetros

Acabo de encontrar una peculiaridad en C que me parece realmente confusa. En C es posible usar un puntero a una estructura antes de que se haya declarado. Esta es una característica muy útil que tiene sentido porque la statement es irrelevante cuando se trata de un puntero a ella. Sin embargo, acabo de encontrar un caso en la esquina donde esto (sorprendentemente) no es cierto, y realmente no puedo explicar por qué. A mi me parece un error en el diseño del lenguaje.

Toma este código:

#include  #include  typedef void (*a)(struct lol* etc); void a2(struct lol* etc) { } int main(void) { return 0; } 

Da:

 foo.c:6:26: warning: 'struct lol' declared inside parameter list [enabled by default] foo.c:6:26: warning: its scope is only this definition or declaration, which is probably not what you want [enabled by default] foo.c:8:16: warning: 'struct lol' declared inside parameter list [enabled by default] 

Para eliminar este problema podemos simplemente hacer esto:

 #include  #include  struct lol* wut; typedef void (*a)(struct lol* etc); void a2(struct lol* etc) { } int main(void) { return 0; } 

El problema inexplicable ahora se ha ido por una razón inexplicable. ¿Por qué?

Tenga en cuenta que esta pregunta es sobre el comportamiento del lenguaje C (o el comportamiento posible del comstackdor de gcc y clang) y no del ejemplo específico que pegué.

EDITAR:

No aceptaré “el orden de la statement es importante” como respuesta a menos que también explique por qué C advierte sobre el uso de un puntero de estructura por primera vez en una lista de argumentos de función, pero lo permite en cualquier otro contexto. ¿Por qué eso posiblemente sería un problema?

Para entender por qué el comstackdor se queja, necesita saber dos cosas acerca de C “struct” s:

  • son creados (como un tipo declarado, pero aún no definido) tan pronto como los nombras, por lo que la primera aparición de struct lol crea una statement.
  • Obedecen las mismas reglas de “statement de scope” que las variables ordinarias.

( struct lol { declara y luego comienza a definir la estructura, es struct lol; o struct lol * o alguna otra cosa que no tenga la llave abierta que se detiene después del paso “declarar”).

Un tipo de estructura que se declara pero aún no está definido es una instancia de lo que C llama un “tipo incompleto”. Se le permite usar punteros a tipos incompletos, siempre y cuando no intente seguir el puntero:

 struct lol *global_p; void f(void) { use0(global_p); /* this is OK */ use1(*global_p); /* this is an error */ use2(global_p->field); /* and so is this */ } 

Debe completar el tipo para “seguir el puntero”, en otras palabras.

En cualquier caso, sin embargo, considere declaraciones de funciones con parámetros int ordinarios:

 int imin2(int a, int b); /* returns a or b, whichever is smaller */ int isum2(int a, int b); /* returns a + b */ 

Las variables denominadas b aquí se declaran dentro del paréntesis, pero esas declaraciones deben salirse del camino para que la siguiente statement de función no se queje de que se las vuelva a declarar.

Lo mismo sucede con struct tag-names:

 void gronk(struct sttag *p); 

La struct sttag declara una estructura, y luego la statement es barrida, al igual que las de b . Pero eso crea un gran problema: la etiqueta se ha ido y ahora no puede nombrar el tipo de estructura nunca más. Si tú escribes:

 struct sttag { int field1; char *field2; }; 

que define un struct sttag nuevo y diferente, como:

 void somefunc(int x) { int y; ... } int x, y; 

define una x una nueva y diferente en el ámbito de nivel de archivo, diferente de las que se encuentran en somefunc .

Afortunadamente, si declara (o incluso define) la estructura antes de escribir la statement de función, la statement de nivel de prototipo “se refiere de nuevo” a la statement de scope externo:

 struct sttag; void gronk(struct sttag *p); 

Ahora, ambos struct sttag s son “el mismo” struct sttag , así que cuando completes struct sttag más tarde, también estarás completando el que está dentro del prototipo para gronk .


Con respecto a la edición de la pregunta: sin duda, habría sido posible definir la acción de las tags struct, union y enum de forma diferente, haciéndolos “burbujear” de prototipos a sus ámbitos de encierro. Eso haría que el problema desapareciera. Pero no fue definido de esa manera. Ya que fue el comité ANSI C89 el que inventó (o robó, en realidad, a partir de entonces C ++) los prototipos, usted puede echarle la culpa a ellos. 🙂

Esto se debe a que, en el primer ejemplo, la estructura no está definida previamente, por lo que el comstackdor intenta tratar esta primera referencia a esa estructura como una definición.

En general, C es un idioma en el que importa el orden de sus declaraciones. Todo lo que use debe declararse adecuadamente de antemano en alguna capacidad, de modo que el comstackdor pueda razonar al respecto cuando se haga referencia en otro contexto.

Esto no es un error o un error en el diseño del lenguaje. Más bien, es una opción que creo que se hizo para simplificar las implementaciones de los primeros comstackdores de C. Las declaraciones adelantadas permiten que un comstackdor traduzca el código fuente en serie en una sola pasada (siempre que se conozca alguna información, como los tamaños y las compensaciones). Si este no fuera el caso, el comstackdor podría ir y venir en el progtwig cada vez que se encuentre con un identificador no reconocido, lo que requerirá que su ciclo de emisión de código sea mucho más complejo.

El comstackdor le está advirtiendo acerca de una statement de struct lol . C te permite hacer esto:

 struct lol; /* forward declaration, the size and members of struct lol are unknown */ 

Esto se usa sobre todo cuando se definen estructuras de autorreferencia, pero también es útil cuando se definen estructuras privadas que nunca se definen en el encabezado. Debido a este último caso de uso, se permite declarar funciones que reciben o devuelven punteros a estructuras incompletas:

 void foo(struct lol *x); 

Sin embargo, solo usar una estructura no declarada en una statement de función, como lo hizo, se interpretará como una statement incompleta local de struct lol cuyo scope está restringido a la función. Esta interpretación está obligada por el estándar C, pero no es útil (no hay forma de construir la struct lol para pasar a la función) y es casi seguro que no es lo que pretendía el progtwigdor, por lo que el comstackdor advierte.