¿Qué parte del estándar C permite comstackr este código?

Estaba solucionando algunos errores de código y el comstackdor advirtió (legítimamente) que la función dynscat() no había sido declarada – la idea de otra persona de un estándar de encoding aceptable – así que rastreé dónde se define la función (lo suficientemente fácil) y qué encabezado declaró (ninguno, ¡Grrr!). Pero esperaba encontrar los detalles de la definición de estructura necesarios para la statement extern de qqparse_val :

 extern struct t_dynstr qqparse_val; extern void dynscat(struct t_dynstr *s, char *p); extern void qqcat(char *s); void qqcat(char *s) { dynscat(&qqparse_val, s); if (*s == ',') dynscat(&qqparse_val, "$"); } 

La función qqcat() en el código original era estática; la statement extern apaga la advertencia del comstackdor para este fragmento del código. La statement de la función dynscat() faltaba por completo; de nuevo, agregarlo reprime una advertencia.

Con el fragmento de código que se muestra, está claro que solo se usa la dirección de la variable, por lo que tiene sentido en un nivel que no importe que los detalles de la estructura no se conozcan. Eran la variable extern struct t_dynstr *p_parseval; , no estarías viendo esta pregunta; eso sería 100% esperado. Si el código necesitaba acceder a las partes internas de la estructura, entonces la definición de la estructura sería necesaria. Pero siempre había esperado que si declaraba que la variable era una estructura (en lugar de un puntero a la estructura), el comstackdor querría saber el tamaño de la estructura, pero aparentemente no.

Intenté provocar a GCC para quejarse, pero no es así, incluso en GCC 4.7.1:

 gcc-4.7.1 -c -Wall -Wextra -std=c89 -pedantic surprise.c 

El código ha estado comstackndo en AIX, HP-UX, Solaris, Linux durante una década, por lo que no es específico de GCC que sea aceptado.

Pregunta

¿Está permitido por el estándar C (principalmente C99 o C11, pero también lo hará C89)? ¿Qué sección? ¿O acabo de dar con un estuche de bola extraña que funciona en todas las máquinas a las que se transfiere pero que no está formalmente sancionado por el estándar?

Parece un caso de tomar la dirección de un objeto con un tipo incompleto.

El uso de punteros a tipos incompletos es totalmente sensato y lo haces cada vez que utilizas un puntero-a-vacío (pero nadie te lo dijo nunca 🙂

Otro caso es si declara algo así como

 extern char a[]; 

No es sorprendente que pueda asignar elementos de a , ¿verdad? Aún así, es un tipo incompleto y los comstackdores le dirán tan pronto como usted haga de tal identificador el operando de un sizeof .

Lo que tiene es un tipo incompleto (ISO / IEC 9899: 1999 y 2011 – todas estas referencias son las mismas en ambos – §6.2.5 ¶22):

Una estructura o tipo de unión de contenido desconocido (como se describe en §6.7.2.3) es un tipo incompleto.

Un tipo incompleto aún puede ser un valor l:

§6.3.2.1 ¶1 (valores L, matrices y designadores de funciones)

Un lvalue es una expresión con un tipo de objeto o un tipo incompleto distinto de void; …

Como resultado, es como cualquier otro unario & con un lvalue.

tu linea

 extern struct t_dynstr qqparse_val; 

Es una statement externa de un objeto, y no una definición. Como objeto externo, “tiene un vínculo”, es decir, un enlace externo.

El estándar dice:

Si se declara un identificador para un objeto sin enlace, el tipo para el objeto se completará al final de su declarador, …

esto implica que si tiene un vínculo, el tipo puede estar incompleto. Entonces no hay problema en hacer &qqparse_val después. Lo que no podría hacer sería sizeof(qqparse_val) ya que el tipo de objeto está incompleto.

Una statement es necesaria para “referirse” a algo. Una definición es necesaria para “usar” algo. Una statement puede proporcionar una definición limitada como en “int a [];” Lo que me detiene es:

 int f(struct _s {int a; int b;} *sp) { sp->a = 1; } 

gcc advierte que ‘struct _s’ se declara dentro de la lista de parámetros. Y declara que “su scope es SOLAMENTE esta definición o statement, …”. Sin embargo, no da un error en “sp-> a” que no está en la lista de parámetros. Al escribir un analizador ‘C’, tuve que decidir dónde terminaba el scope de la definición.

Enfocando en la primera línea:

 extern struct t_dynstr qqparse_val; 

Se puede dividir en pasos separados para crear el tipo y la variable, lo que da como resultado este par de líneas equivalentes:

 struct t_dynstr; /* declaration of an incomplete (opaque) struct type */ extern struct t_dynstr qqparse_val; /* declaration of an object of that type */ 

La segunda línea se parece al original, pero ahora se refiere al tipo que ya existe debido a la primera línea.

La primera línea funciona porque así es como se hacen las estructuras opacas.

La segunda línea funciona porque no necesita un tipo completo para hacer una statement externa.

La combinación (la segunda línea funciona sin la primera línea) funciona porque la combinación de una statement de tipo con una statement de variable funciona en general. Todos estos usan el mismo principio:

 struct { int x,y; } loc; /* define a nameless type and a variable of that type */ struct point { int x,y; } location; /* same but the type has a name */ union u { int i; float f; } u1, u2; /* one type named "union u", two variables */ 

Parece un poco gracioso con el extern seguido inmediatamente por una statement de tipo, como si estuvieras tratando de hacer que el tipo sea “externo”, lo cual no tiene sentido. Pero eso no es lo que significa. El extern aplica al qqparse_val a pesar de su separación geográfica.

Aquí están mis pensamientos relativos al estándar (C11).

Sección 6.5.3.2: Operadores de dirección e indirección

Restricciones

Párrafo 1 : El operando del operador y unario debe ser un designador de función, el resultado de un operador [] o unario *, o un valor l que designa un objeto que no es un campo de bits y no se declara con el almacenamiento de registro -class specifier.

Párrafo 2 : El operando del operador unario * tendrá un tipo de puntero.

Aquí, no especificamos ningún requisito sobre el objeto, aparte de que es un objeto (y no un campo de bits o registro).

Por otro lado, veamos sizeof.

6.5.3.4 Los operadores sizeof y _Alignof

Restricciones

Párrafo 1: El operador sizeof no se aplicará a una expresión que tenga un tipo de función o un tipo incompleto, al nombre entre paréntesis de dicho tipo, o a una expresión que designe un miembro de campo de bit. El operador _Alignof no se aplicará a un tipo de función o un tipo incompleto.

Aquí, el estándar requiere explícitamente que el objeto no sea un tipo incompleto.

Por lo tanto, creo que este es un caso de lo que no se niega explícitamente.