tornillo - ¿Los miembros flexibles de la matriz pueden conducir a un comportamiento indefinido?
bombas de miembro flexible (3)
Al usar miembros de matriz flexibles (FAM) dentro de los tipos de estructura, ¿estamos exponiendo nuestros programas a la posibilidad de un comportamiento indefinido?
¿Es posible que un programa use FAMs y aún así sea un programa estrictamente conforme?
¿Se requiere que el desplazamiento del miembro flexible de la matriz esté al final de la estructura?
Las preguntas se aplican tanto a C99 (TC3)
como a C11 (TC1)
.
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
int main(void) {
struct s {
size_t len;
char pad;
int array[];
};
struct s *s = malloc(sizeof *s + sizeof *s->array);
printf("sizeof *s: %zu/n", sizeof *s);
printf("offsetof(struct s, array): %zu/n", offsetof(struct s, array));
s->array[0] = 0;
s->len = 1;
printf("%d/n", s->array[0]);
free(s);
return 0;
}
Salida:
sizeof *s: 16
offsetof(struct s, array): 12
0
La respuesta corta
Sí. Las convenciones comunes de usar FAM exponen nuestros programas a la posibilidad de un comportamiento indefinido. Habiendo dicho eso, no estoy al tanto de ninguna implementación existente que se comporte mal.
Posible, pero poco probable. Incluso si no alcanzamos un comportamiento no definido, es probable que fallemos en la conformidad estricta.
No. No se requiere que el desplazamiento de FAM esté al final de la estructura, puede superponer cualquier octeto de relleno final.
Las respuestas se aplican tanto a C99 (TC3)
como a C11 (TC1)
.
La respuesta larga
Los FAM se introdujeron por primera vez en C99 (TC0) (diciembre de 1999), y su especificación original requería que el desplazamiento de FAM estuviera al final de la estructura. La especificación original estaba bien definida y, como tal, no podía conducir a un comportamiento indefinido o ser un problema con respecto a la conformidad estricta.
C99 (TC0) §6.7.2.1 p16
(diciembre de 1999)
[Este documento es el estándar oficial, tiene derechos de autor y no está disponible gratuitamente]
El problema era que las implementaciones C99 comunes, como GCC, no seguían los requisitos del estándar, y permitían al FAM superponer los bytes de relleno posteriores. Se consideró que su enfoque era más eficiente, y dado que para ellos seguir el requisito de la norma, se produciría una ruptura de compatibilidad con versiones anteriores, el comité optó por cambiar la especificación y, a partir de C99 TC2 (noviembre de 2004), la norma ya no es necesaria. el desplazamiento de FAM está al final de la estructura.
C99 (TC2) §6.7.2.1 p16
(nov. 2004)
[...] el tamaño de la estructura es como si se hubiera omitido el miembro flexible de la matriz, excepto que puede tener más relleno posterior que la omisión implicaría.
La nueva especificación eliminó la afirmación que requería que el desplazamiento del FAM estuviera al final de la estructura, e introdujo una consecuencia muy desafortunada, porque el estándar le da a la implementación la libertad de no mantener los valores de los bytes de relleno dentro de las estructuras o sindicatos en un estado consistente. Más específicamente:
C99 (TC3) §6.2.6.1 p6
Cuando se almacena un valor en un objeto de estructura o tipo de unión, incluido en un objeto miembro, los bytes de la representación de objeto que corresponden a cualquier byte de relleno toman valores no especificados.
Esto significa que si alguno de nuestros elementos FAM corresponde (o superpone) a cualquier octeto de relleno posterior, al almacenarlo en un miembro de la estructura, pueden (tomarán) valores no especificados. Ni siquiera necesitamos pensar si esto se aplica a un valor almacenado en la propia FAM, incluso la interpretación estricta de que esto solo se aplica a miembros que no sean la FAM, es lo suficientemente dañino.
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
int main(void) {
struct s {
size_t len;
char pad;
int array[];
};
struct s *s = malloc(sizeof *s + sizeof *s->array);
if (sizeof *s > offsetof(struct s, array)) {
s->array[0] = 123;
s->len = 1; /* any padding bytes take unspecified values */
printf("%d/n", s->array[0]); /* indeterminate value */
}
free(s);
return 0;
}
Una vez que almacenamos en un miembro de la estructura, los bytes de relleno toman bytes no especificados y, por lo tanto, cualquier suposición sobre los valores de los elementos de FAM que corresponden a cualquier octeto de relleno final, ahora es falso. Lo que significa que cualquier suposición nos lleva a un cumplimiento estricto.
Comportamiento indefinido
Aunque los valores de los bytes de relleno son "valores no especificados", no se puede decir lo mismo sobre el tipo que se ve afectado por ellos, porque una representación de objeto que se basa en valores no especificados puede generar una representación de trampa. Entonces, el único término estándar que describe estas dos posibilidades sería "valor indeterminado". Si el tipo de FAM tiene representaciones de trampas, acceder a ellas no es solo una preocupación de un valor no especificado, sino un comportamiento indefinido.
Pero espera hay mas. Si aceptamos que el único término estándar para describir dicho valor es como un "valor indeterminado", incluso si el tipo de FAM no tiene representaciones de trampa, hemos alcanzado un comportamiento indefinido, ya que la interpretación oficial de la C El comité de estándares es que pasar valores indeterminados a las funciones de la biblioteca estándar es un comportamiento indefinido.
Si se permite que un programa estrictamente conforme haga uso del comportamiento definido en la implementación en casos en los que "funcionaría" con todos los comportamientos legítimos (a pesar de que casi cualquier tipo de resultado útil dependería de detalles definidos por la implementación, como el conjunto de caracteres de ejecución) ), el uso de miembros de matriz flexible dentro de un programa de estricto ajuste debería ser posible siempre que al programa no le importe si el desplazamiento del miembro de matriz flexible coincide con la longitud de la estructura.
No se considera que las matrices tengan ningún relleno interno, por lo que cualquier relleno que se agregue debido al FAM lo antecedería. Si hay suficiente espacio dentro o más allá de una estructura para acomodar miembros en una FAM, esos miembros son parte de la FAM. Por ejemplo, dado:
struct { long long x; char y; short z[]; } foo;
el tamaño de "foo" puede rellenarse más allá del inicio de z
debido a la alineación, pero cualquier relleno de ese tipo se podrá usar como parte de z
. Escribir y
puede perturbar el relleno que precede a z
, pero no debe alterar ninguna parte de z
.
Esta es una respuesta larga que trata extensamente un tema complicado.
TL; DR
No estoy de acuerdo con el analysis de
El problema clave es un malentendido de lo que significa el §6.2.1 ¶6 en los estándares C99 y C11, y aplicarlo de manera inapropiada a una asignación entera simple como, por ejemplo:
fam_ptr->nonfam_member = 23;
Esta asignación no tiene permitido cambiar ningún byte de relleno en la estructura apuntada por fam_ptr
. En consecuencia, el análisis basado en la presunción de que esto puede cambiar los bytes de relleno en la estructura es erróneo.
Fondo
En principio, no estoy terriblemente preocupado por el estándar C99 y sus correcciones; no son el estándar actual. Sin embargo, la evolución de la especificación de los miembros de matriz flexible es informativa.
El estándar C99 - ISO / IEC 9899: 1999 - tenía 3 correcciones técnicas:
- TC1 lanzado el 2001-09-01 (7 páginas),
- TC2 lanzado el 15/11/2004 (15 páginas),
- TC3 lanzado en 2007-11-15 (10 páginas).
Fue TC3, por ejemplo, que declaró que gets()
era obsoleto y obsoleto, lo que llevó a eliminarlo del estándar C11.
El estándar C11 - ISO / IEC 9899: 2011 - tiene un corrigendum técnico, pero simplemente establece el valor de dos macros accidentalmente dejadas en el formato 201ymmL
- los valores requeridos para __STDC_VERSION__
y __STDC_LIB_EXT1__
se corrigieron al valor 201112L
. (Puede ver el TC1 - formalmente "ISO / IEC 9899: 2011 / Cor.1: 2012 (es) Tecnología de la información - Lenguajes de programación - C CORRIGENDUM TÉCNICO 1" - en https://www.iso.org/obp/ui/#iso:std:iso-iec:9899:ed-3:v1:cor:1:v1:en . No he averiguado cómo se puede descargar, pero es tan simple que realmente no funciona importa mucho
Estándar C99 en miembros de matriz flexible
ISO / CEI 9899: 1999 (antes TC2) §6.7.2.1 ¶16:
Como un caso especial, el último elemento de una estructura con más de un miembro nombrado puede tener un tipo de matriz incompleto; esto se llama un miembro de matriz flexible . Con dos excepciones, el miembro de matriz flexible se ignora. En primer lugar, el tamaño de la estructura debe ser igual al desplazamiento del último elemento de una estructura por lo demás idéntica que reemplaza el miembro de matriz flexible con una matriz de longitud no especificada. 106) Segundo, cuando a
.
(o->
) el operador tiene un operando izquierdo que es (un puntero) una estructura con un miembro de matriz flexible y el operando derecho nombra ese miembro, se comporta como si ese miembro se reemplazara con la matriz más larga (con el mismo tipo de elemento) ) que no haría la estructura más grande que el objeto al que se accede; el desplazamiento de la matriz seguirá siendo el del miembro de matriz flexible, incluso si esto difiriera del de la matriz de reemplazo. Si esta matriz no tiene elementos, se comporta como si tuviera un elemento, pero el comportamiento no está definido si se intenta acceder a ese elemento o generar un puntero pasado.126) La longitud no está especificada para tener en cuenta el hecho de que las implementaciones pueden dar a los miembros de la matriz diferentes alineamientos según sus longitudes.
(Esta nota a pie de página se elimina en la reescritura). El estándar original C99 incluía un ejemplo:
¶17 EJEMPLO Suponiendo que todos los miembros de la matriz están alineados de la misma manera, después de las declaraciones:
struct s { int n; double d[]; }; struct ss { int n; double d[1]; };
las tres expresiones:
sizeof (struct s) offsetof(struct s, d) offsetof(struct ss, d)
tener el mismo valor La estructura struct s tiene un miembro de matriz flexible d.
¶18 Si sizeof (double) es 8, luego de ejecutar el siguiente código:
struct s *s1; struct s *s2; s1 = malloc(sizeof (struct s) + 64); s2 = malloc(sizeof (struct s) + 46);
y suponiendo que las llamadas a malloc tienen éxito, los objetos apuntados por s1 y s2 se comportan como si los identificadores hubieran sido declarados como:
struct { int n; double d[8]; } *s1; struct { int n; double d[5]; } *s2;
¶19 Después de las siguientes asignaciones exitosas:
s1 = malloc(sizeof (struct s) + 10); s2 = malloc(sizeof (struct s) + 6);
luego se comportan como si las declaraciones fueran:
struct { int n; double d[1]; } *s1, *s2;
y:
double *dp; dp = &(s1->d[0]); // valid *dp = 42; // valid dp = &(s2->d[0]); // valid *dp = 42; // undefined behavior
¶20 La tarea:
*s1 = *s2;
solo copia el miembro ny ninguno de los elementos de la matriz. Similar:
struct s t1 = { 0 }; // valid struct s t2 = { 2 }; // valid struct ss tt = { 1, { 4.2 }}; // valid struct s t3 = { 1, { 4.2 }}; // invalid: there is nothing for the 4.2 to initialize t1.n = 4; // valid t1.d[0] = 4.2; // undefined behavior
Parte de este material de ejemplo se eliminó en C11. El cambio no se observó (y no fue necesario señalar) en TC2 porque los ejemplos no son normativos. Pero el material reescrito en C11 es informativo cuando se estudia.
Documento N983 que identifica un problema con los miembros de matriz flexibles
N983 del N983 WG14 Pre-Santa Cruz-2002 es, creo, la declaración inicial de un informe de defectos. Establece que algunos compiladores C (citando tres) logran poner un FAM antes del relleno al final de una estructura. El informe final del defecto fue DR 282 .
Según tengo entendido, este informe condujo al cambio en TC2, aunque no he rastreado todos los pasos en el proceso. Parece que el DR ya no está disponible por separado.
TC2 usó la redacción encontrada en el estándar C11 en el material normativo.
C11 estándar en miembros de matriz flexible
Entonces, ¿qué tiene que decir el estándar C11 sobre los miembros del arreglo flexible?
§6.7.2.1 Estructura y especificadores de unión
¶3 Una estructura o unión no debe contener un miembro con un tipo incompleto o de función (por lo tanto, una estructura no debe contener una instancia de sí mismo, pero puede contener un puntero a una instancia de sí mismo), excepto que el último miembro de una estructura con más de un miembro nombrado puede tener un tipo de matriz incompleta; dicha estructura (y cualquier unión que contenga, posiblemente recursivamente, un miembro que sea tal estructura) no deberá ser un miembro de una estructura o un elemento de una matriz.
Esto posiciona firmemente al FAM al final de la estructura - ''el último miembro'' es por definición al final de la estructura, y esto se confirma por:
¶15 Dentro de un objeto de estructura, los miembros de campo que no son de bit y las unidades en que residen los campos de bit tienen direcciones que aumentan en el orden en que se declaran.
¶17 Puede haber un relleno sin nombre al final de una estructura o unión.
¶18 Como un caso especial, el último elemento de una estructura con más de un miembro nombrado puede tener un tipo de matriz incompleto; esto se llama un miembro de matriz flexible . En la mayoría de las situaciones, el miembro de matriz flexible se ignora. En particular, el tamaño de la estructura es como si se hubiera omitido el miembro flexible de la matriz, excepto que puede tener más relleno posterior de lo que implicaría la omisión. Sin embargo, cuando a
.
(o->
) el operador tiene un operando izquierdo que es (un puntero) una estructura con un miembro de matriz flexible y el operando derecho nombra ese miembro, se comporta como si ese miembro se reemplazara con la matriz más larga (con el mismo tipo de elemento) ) que no haría la estructura más grande que el objeto al que se accede; el desplazamiento de la matriz seguirá siendo el del miembro de matriz flexible, incluso si esto difiriera del de la matriz de reemplazo. Si esta matriz no tiene elementos, se comporta como si tuviera un elemento, pero el comportamiento no está definido si se intenta acceder a ese elemento o generar un puntero pasado.
Este párrafo contiene el cambio en ¶20 de ISO / IEC 9899: 1999 / Cor.2: 2004 (E) - el TC2 para C99;
La información al final de la parte principal de una estructura que contiene un miembro de matriz flexible es un relleno de seguimiento regular que puede ocurrir con cualquier tipo de estructura. No se puede acceder legítimamente a dicho relleno, pero se puede pasar a las funciones de la biblioteca, etc. a través de punteros a la estructura sin incurrir en un comportamiento indefinido.
El estándar C11 contiene tres ejemplos, pero el primero y el tercero están relacionados con estructuras y uniones anónimas en lugar de la mecánica de los miembros de matriz flexible. Recuerde, los ejemplos no son ''normativos'', pero son ilustrativos.
¶20 EJEMPLO 2 Después de la declaración:
struct s { int n; double d[]; };
la estructura
struct s
tiene un miembro de matriz flexibled
. Una forma típica de usar esto es:
int m = /* some value */; struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));
y suponiendo que la llamada a
malloc
tiene éxito, el objeto apuntado porp
comporta, para la mayoría de los propósitos, como sip
hubiera sido declarado como:
struct { int n; double d[m]; } *p;
(hay circunstancias en las que esta equivalencia se rompe, en particular, las compensaciones del miembro
d
pueden no ser las mismas).¶21 Siguiendo la declaración anterior:
struct s t1 = { 0 }; // valid struct s t2 = { 1, { 4.2 }}; // invalid t1.n = 4; // valid t1.d[0] = 4.2; // might be undefined behavior
La inicialización de
t2
no es válida (y viola una restricción) porquestruct s
se trata como si no contuviera el miembrod
. La asignación at1.d[0]
probablemente sea un comportamiento indefinido, pero es posible que
sizeof (struct s) >= offsetof(struct s, d) + sizeof (double)
en cuyo caso la asignación sería legítima. Sin embargo, no puede aparecer en un código estrictamente conforme.
¶22 Después de la declaración adicional:
struct ss { int n; };
las expresiones:
sizeof (struct s) >= sizeof (struct ss) sizeof (struct s) >= offsetof(struct s, d)
siempre son iguales a 1.
¶23 Si
sizeof (double)
es 8, luego de ejecutar el siguiente código:
struct s *s1; struct s *s2; s1 = malloc(sizeof (struct s) + 64); s2 = malloc(sizeof (struct s) + 46);
y suponiendo que las llamadas a
malloc
tienen éxito, los objetos señalados pors1
ys2
comportan, para la mayoría de los fines, como si los identificadores hubieran sido declarados como:
struct { int n; double d[8]; } *s1; struct { int n; double d[5]; } *s2;
¶24 Después de las siguientes asignaciones exitosas:
s1 = malloc(sizeof (struct s) + 10); s2 = malloc(sizeof (struct s) + 6);
luego se comportan como si las declaraciones fueran:
struct { int n; double d[1]; } *s1, *s2;
y:
double *dp; dp = &(s1->d[0]); // valid *dp = 42; // valid dp = &(s2->d[0]); // valid *dp = 42; // undefined behavior
¶25 La asignación:
*s1 = *s2;
solo copia al miembro
n
; si alguno de los elementos de la matriz está dentro del primersizeof (struct s)
bytes de la estructura, se pueden copiar o simplemente sobrescribir con valores indeterminados.
Tenga en cuenta que esto cambió entre C99 y C11.
Otra parte del estándar describe este comportamiento de copiado:
§6.2.6 Representación de tipos §6.2.6.1 General
¶6 Cuando se almacena un valor en un objeto de estructura o tipo de unión, incluido en un objeto miembro, los bytes de la representación de objeto que corresponden a cualquier byte de relleno toman valores no especificados. 51) El valor de una estructura o de un objeto de unión nunca es una representación de trampa, aunque el valor de un miembro de la estructura o del objeto de unión puede ser una representación de trampa.
51) Por lo tanto, por ejemplo, la asignación de estructura no necesita copiar ningún bit de relleno.
Ilustrando la problemática estructura FAM
En la sala de chat C , wrote información de la cual esta es una paráfrasis:
Considerar:
struct fam1 { double d; char c; char fam[]; };
Suponer que el doble requiere alineación de 8 bytes (o 4 bytes, no importa demasiado, pero me quedaré con 8), luego struct non_fam1a { double d; char c; };
struct non_fam1a { double d; char c; };
tendría 7 bytes de relleno después de c
un tamaño de 16. Además, struct non_fam1b { double d; char c; char nonfam[4]; };
struct non_fam1b { double d; char c; char nonfam[4]; };
tendría un relleno de 3 bytes después de la matriz no nonfam
y un tamaño de 16.
La sugerencia es que el inicio de fam
in struct fam1
puede estar en el desplazamiento 9, aunque sizeof(struct fam1)
es 16. Entonces, los bytes después de c
no son de relleno (necesariamente).
Por lo tanto, para una FAM suficientemente pequeña, el tamaño de la estructura más FAM podría ser menor que el tamaño de struct fam
.
La asignación prototípica es:
struct fam1 *fam = malloc(sizeof(struct fam1) + array_size * sizeof(char));
cuando el FAM es de tipo char
(como en struct fam1
). Esa es una sobreestimación (bruta) cuando el desplazamiento de fam es menor que sizeof(struct fam1)
.
Existen macros para calcular el almacenamiento requerido "preciso" basado en compensaciones FAM que son menores que el tamaño de la estructura. Tal como este: https://gustedt.wordpress.com/2011/03/14/flexible-array-member/
Dirigiéndose a la pregunta
La pregunta pregunta:
- Al usar miembros de matriz flexibles (FAM) dentro de los tipos de estructura, ¿estamos exponiendo nuestros programas a la posibilidad de un comportamiento indefinido?
- ¿Es posible que un programa use FAMs y aún así sea un programa estrictamente conforme?
- ¿Se requiere que el desplazamiento del miembro flexible de la matriz esté al final de la estructura?
Las preguntas se aplican tanto a C99 (TC3) como a C11 (TC1).
Creo que si codifica correctamente, las respuestas son "No", "Sí", "No y Sí, dependiendo ...".
Pregunta 1
Asumo que la intención de la pregunta 1 es "¿debe su programa estar inevitablemente expuesto a un comportamiento indefinido si usa cualquier FAM en algún lugar?" Para expresar lo que creo que es obvio: hay muchas maneras de exponer un programa a un comportamiento indefinido (y algunas de esas formas involucran estructuras con miembros de matriz flexibles).
No creo que el simple hecho de usar un FAM signifique que el programa tiene automáticamente (invoca, está expuesto a) un comportamiento indefinido.
Pregunta 2
Sección §4 La conformidad define:
¶5 Un programa estrictamente conforme deberá usar solo aquellas características del idioma y la biblioteca especificadas en esta norma internacional. 3) No producirá salida dependiente de ningún comportamiento no especificado, indefinido o definido por la implementación, y no deberá exceder ningún límite mínimo de implementación.
3) Un programa estrictamente conforme puede usar características condicionales (ver 6.10.8.3) siempre que el uso esté protegido por una directiva apropiada de preprocesamiento de inclusión condicional que utilice la macro relacionada. ...
¶7 Un programa conforme es aquel que es aceptable para una implementación conforme. 5) .
5) Los programas estrictamente conformes están destinados a ser totalmente portátiles entre implementaciones conformes. Los programas conformes pueden depender de características no portables de una implementación conforme.
No creo que haya ninguna característica de la norma C que, si se utiliza de la manera que la norma pretende, haga que el programa no se ajuste estrictamente. Si los hay, están relacionados con el comportamiento dependiente de la configuración regional. El comportamiento del código FAM no depende intrínsecamente de la configuración regional.
No creo que el uso de un FAM signifique inherentemente que el programa no se ajuste estrictamente.
Pregunta 3
Creo que la pregunta 3 es ambigua entre:
- 3A: ¿Se requiere que el desplazamiento del elemento de matriz flexible sea igual al tamaño de la estructura que contiene el miembro de matriz flexible?
- 3B: ¿Se requiere que el desplazamiento del elemento de matriz flexible sea mayor que el desplazamiento de cualquier miembro anterior de la estructura?
La respuesta a 3A es "No" (véase el ejemplo de C11 en ¶25, citado anteriormente).
La respuesta a 3B es "Sí" (testigo §6.7.2.1 ¶15, citado anteriormente).
Disentir de la respuesta de Dror
Necesito citar el estándar C y la respuesta de Dror. Usaré [DK]
para indicar el comienzo de una cita de la respuesta de Dror, y las citas sin marcar son del estándar C.
A partir de 2017-07-01 18:00 -08: 00, la breve respuesta de Dror K dijo:
[DK]
- Sí. Las convenciones comunes de usar FAM exponen nuestros programas a la posibilidad de un comportamiento indefinido. Habiendo dicho eso, no estoy al tanto de ninguna implementación existente que se comporte mal.
No estoy convencido de que simplemente usar un FAM signifique que el programa tiene un comportamiento indefinido automáticamente.
[DK]
- Posible, pero poco probable. Incluso si no alcanzamos un comportamiento no definido, es probable que fallemos en la conformidad estricta.
No estoy convencido de que el uso de un FAM represente automáticamente un programa que no se ajuste estrictamente.
[DK]
- No. No se requiere que el desplazamiento de FAM esté al final de la estructura, puede superponer cualquier octeto de relleno final.
Esta es la respuesta a mi interpretación 3A, y estoy de acuerdo con esto.
La respuesta larga contiene la interpretación de las respuestas cortas anteriores.
[DK]
El problema era que las implementaciones C99 comunes, como GCC, no seguían los requisitos del estándar, y permitían al FAM superponer los bytes de relleno posteriores. Se consideró que su enfoque era más eficiente, y dado que para ellos seguir el requisito de la norma, se produciría una ruptura de compatibilidad con versiones anteriores, el comité optó por cambiar la especificación y, a partir de C99 TC2 (noviembre de 2004), la norma ya no es necesaria. el desplazamiento de FAM está al final de la estructura.
Estoy de acuerdo con este análisis.
[DK]
La nueva especificación eliminó la afirmación que requería que el desplazamiento del FAM estuviera al final de la estructura, e introdujo una consecuencia muy desafortunada, porque el estándar le da a la implementación la libertad de no mantener los valores de los bytes de relleno dentro de las estructuras o sindicatos en un estado consistente.
Estoy de acuerdo en que la nueva especificación eliminó el requisito de que FAM se almacene en un desplazamiento mayor o igual que el tamaño de la estructura.
No estoy de acuerdo con que haya un problema con los bytes de relleno.
El estándar explícitamente dice que la asignación de estructura para una estructura que contiene un FAM efectivamente ignora el FAM (§6.7.2.1 ¶18). Debe copiar a los miembros que no son FAM. Se establece explícitamente que los bytes de relleno no necesitan copiarse en absoluto (§6.2.6.1 ¶6 y nota al pie 51). Y el Ejemplo 2 establece explícitamente (no normativamente §6.7.2.1 ¶25) que si el FAM se superpone al espacio definido por la estructura, los datos de la parte del FAM que se solapa con el final de la estructura podrían o no ser copiado
[DK]
Esto significa que si alguno de nuestros elementos FAM corresponde (o superpone) a cualquier octeto de relleno posterior, al almacenarlo en un miembro de la estructura, pueden (tomarán) valores no especificados. Ni siquiera necesitamos pensar si esto se aplica a un valor almacenado en la propia FAM, incluso la interpretación estricta de que esto solo se aplica a miembros que no sean la FAM, es lo suficientemente dañino.
No veo esto como un problema. Cualquier expectativa de que se pueda copiar una estructura que contenga una asignación de estructura con FAM y se copie la matriz FAM es intrínsecamente defectuosa: la copia deja los datos de FAM lógicamente sin copia. Cualquier programa que depende de los datos de FAM dentro del alcance de la estructura está roto; eso es una propiedad del programa (defectuoso), no del estándar.
[DK]
#include <stdio.h> #include <stdlib.h> #include <stddef.h> int main(void) { struct s { size_t len; char pad; int array[]; }; struct s *s = malloc(sizeof *s + sizeof *s->array); if (sizeof *s > offsetof(struct s, array)) { s->array[0] = 123; s->len = 1; /* any padding bytes take unspecified values */ printf("%d/n", s->array[0]); /* indeterminate value */ } free(s); return 0; }
Idealmente, por supuesto, el código establecería el miembro del pad
asociado a un valor determinado, pero eso no causa realmente un problema ya que nunca se accede.
Enfáticamente estoy en desacuerdo con que el valor de s->array[0]
en printf()
sea indeterminado; su valor es 123
.
La cita estándar anterior es (es la misma §6.2.6.1 ¶6 tanto en C99 como en C11, aunque el número de la nota al pie es 42 en C99 y 51 en C11):
Cuando se almacena un valor en un objeto de estructura o tipo de unión, incluido en un objeto miembro, los bytes de la representación de objeto que corresponden a cualquier byte de relleno toman valores no especificados.
Tenga en cuenta que s->len
no es una asignación a un objeto de estructura o tipo de unión; es una asignación a un objeto de tipo size_t
. Creo que esta puede ser la principal fuente de confusión aquí.
Si el código incluye:
struct s *t = malloc(sizeof(*t) + sizeof(t->array[0]));
*t = *s;
printf("t->array[0] = %d/n", t->array[0]);
entonces el valor impreso es de hecho indeterminado. Sin embargo, eso se debe a que no se garantiza que copiar una estructura con un FAM copie el FAM. Más código casi correcto sería (suponiendo que agregue #include <string.h>
, por supuesto):
struct s *t = malloc(sizeof(*t) + sizeof(t->array[0]));
*t = *s;
memmmove(t->array, s->array, sizeof(t->array[0]));
printf("t->array[0] = %d/n", t->array[0]);
Ahora el valor impreso es determinado (es 123
). Tenga en cuenta que la condición de if (sizeof *s > offsetof(struct s, array))
es irrelevante para mi análisis.
Dado que el resto de la respuesta larga (principalmente la sección identificada por el encabezado "comportamiento indefinido") se basa en una falsa inferencia sobre la posibilidad de que los bytes de relleno de una estructura cambien cuando se asigna a un miembro entero de una estructura, el resto de la discusión no necesita más análisis.
[DK]
Una vez que almacenamos en un miembro de la estructura, los bytes de relleno toman bytes no especificados y, por lo tanto, cualquier suposición sobre los valores de los elementos de FAM que corresponden a cualquier octeto de relleno final, ahora es falso. Lo que significa que cualquier suposición nos lleva a un cumplimiento estricto.
Esto se basa en una premisa falsa; la conclusión es falsa