example - typedef struct c
Matriz de tamaƱo 0 al final de struct (5)
Actualmente, existe una característica estándar, como se menciona en C11, capítulo §6.7.2.1, llamado miembro de matriz flexible .
Citando el estándar,
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. [...]
La sintaxis debería ser
struct s { int n; double d[]; };
donde el último elemento es tipo incompleto, ( sin dimensiones de matriz, ni siquiera 0 ).
Entonces, tu código debería verse mejor
struct array{
size_t size;
int data[ ];
};
para ser de conformidad estándar.
Ahora, volviendo a su ejemplo, de una matriz de 0 tamaños, esta fue una forma heredada ( "struct hack" ) de lograr lo mismo. Antes de C99
, GCC lo soportaba como una extensión para emular la funcionalidad flexible de los miembros de la matriz.
Esta pregunta ya tiene una respuesta aquí:
Mi profesor de un curso de programación de sistemas que estoy tomando nos dijo hoy que definamos una estructura con una matriz de longitud cero al final:
struct array{
size_t size;
int data[0];
};
typedef struct array array;
Esta es una estructura útil para definir o inicializar una matriz con una variable, es decir, algo de la siguiente manera:
array *array_new(size_t size){
array* a = malloc(sizeof(array) + size * sizeof(int));
if(a){
a->size = size;
}
return a;
}
Es decir, usando malloc()
, también asignamos memoria para la matriz de tamaño cero. Esto es completamente nuevo para mí, y parece extraño, porque, desde mi comprensión, las estructuras no tienen sus elementos necesariamente en ubicaciones continuas.
¿Por qué el código en array_new
asigna memoria a los data[0]
? ¿Por qué sería legal acceder entonces?
array * a = array_new(3);
a->data[1] = 12;
?
Por lo que nos dijo, parece que una matriz definida como longitud cero al final de una estructura está asegurada para venir inmediatamente después del último elemento de la estructura, pero esto parece extraño, porque, de nuevo, desde mi comprensión, las estructuras podrían tener relleno.
También he visto que esta es solo una característica de gcc y no está definida por ningún estándar. ¿Es esto cierto?
La forma en que solía hacerlo es sin un miembro ficticio al final de la estructura: el tamaño de la estructura en sí le dice la dirección que acaba de pasar. Agregar 1 al puntero tipeado va allí:
header * p = malloc (sizeof (header) + buffersize);
char * buffer = (char*)(p+1);
En cuanto a las estructuras en general, puede saber que los campos están ordenados en orden. Ser capaz de hacer coincidir alguna estructura impuesta necesaria por una imagen binaria en formato de archivo, llamada al sistema operativo o hardware es una ventaja del uso de C. Debe saber cómo funciona el relleno para la alineación, pero están en orden y en un bloque contiguo.
Otras respuestas explican que las matrices de longitud cero son extensiones de GCC y C permite la variedad de longitud variable, pero nadie respondió a las otras preguntas.
desde mi punto de vista, las estructuras no tienen sus elementos necesariamente en ubicaciones continuas.
Sí. struct
tipo de datos struct
no tiene sus elementos necesariamente en ubicaciones continuas.
¿Por qué el código en
array_new
asigna memoria a losdata[0]
? ¿Por qué sería legal acceder entonces?
array * a = array_new(3); a->data[1] = 12;
?
Debe tener en cuenta que una de las restricciones de la matriz de longitud cero es que debe ser el último miembro de una estructura. Con esto, el compilador sabe que la estructura puede tener un objeto de longitud variable y se necesitará más memoria en el tiempo de ejecución.
Pero, no debes confundirte con; " dado que la matriz de longitud cero es el último miembro de la estructura, entonces la memoria asignada para la matriz de longitud cero debe agregarse al final de la estructura y dado que las estructuras no tienen sus elementos necesariamente en ubicaciones continuas, ¿cómo podría esa memoria asignada ser ¿accedido? "
No. Ese no es el caso. La asignación de memoria para los miembros de la estructura no necesariamente es contigua, puede haber espacio entre ellos, pero se debe acceder a esa memoria asignada con data
variables. Y sí, el relleno no tendrá ningún efecto aquí. La regla es: §6.7.2.1 / 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.
También he visto que esta es solo una característica de gcc y no está definida por ningún estándar. ¿Es esto cierto?
Sí. Como otras respuestas ya se mencionó, las matrices de longitud cero no son compatibles con la norma C, sino una extensión de los compiladores de GCC. C99 introdujo un miembro de matriz flexible . Un ejemplo del estándar C (6.7.2.1):
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).
Tu profesor está confundido. Deberían leer lo que sucede si defino una matriz de tamaño cero . Esta es una extensión GCC no estándar; no es válido C y no es algo que deben enseñar a los estudiantes a usar (*).
En su lugar, use un miembro de matriz flexible C estándar. A diferencia de su matriz de tamaño cero, en realidad funcionará, de manera portátil:
struct array{
size_t size;
int data[];
};
Se garantiza que los miembros flexibles de la matriz cuenten como cero cuando usa sizeof
en la estructura, lo que le permite hacer cosas como:
malloc(sizeof(array) + sizeof(int[size]));
(*) En los años 90, las personas usaban un exploit inseguro para agregar datos después de las estructuras, lo que se conoce como "struct hack". Para proporcionar una forma segura de extender una estructura, GCC implementó la función de matriz de tamaño cero como una extensión no estándar. Se volvió obsoleto en 1999 cuando el estándar C finalmente proporcionó una mejor manera de hacer esto.
Una forma más estándar sería definir su matriz con un tamaño de datos de 1, como en:
struct array{
size_t size;
int data[1]; // <--- will work across compilers
};
Luego use el desplazamiento del miembro de datos (no el tamaño de la matriz) en el cálculo:
array *array_new(size_t size){
array* a = malloc(offsetof(array, data) + size * sizeof(int));
if(a){
a->size = size;
}
return a;
}
Esto está usando efectivamente array.data como un marcador de hacia dónde irán los datos adicionales (dependiendo del tamaño).