c arrays data-structures flexible-array-member

¿Está utilizando miembros de matriz flexible en C mala práctica?



arrays data-structures (7)

Como nota al margen, para la compatibilidad C89, dicha estructura debe asignarse como:

struct header *my_header = malloc(offsetof(struct header, data) + n * sizeof my_header->data);

O con macros:

#define FLEXIBLE_SIZE SIZE_MAX /* or whatever maximum length for an array */ #define SIZEOF_FLEXIBLE(type, member, length) / ( offsetof(type, member) + (length) * sizeof ((type *)0)->member[0] ) struct header { size_t len; unsigned char data[FLEXIBLE_SIZE]; }; ... size_t n = 123; struct header *my_header = malloc(SIZEOF_FLEXIBLE(struct header, data, n));

Establecer FLEXIBLE_SIZE en SIZE_MAX casi garantiza que esto fallará:

struct header *my_header = malloc(sizeof *my_header);

Recientemente leí que usar miembros de matriz flexible en C era una práctica de ingeniería de software deficiente. Sin embargo, esa afirmación no estaba respaldada por ningún argumento. ¿Es esto un hecho aceptado?

( Los miembros de matriz flexible son una característica C introducida en C99 donde se puede declarar que el último elemento es una matriz de tamaño no especificado. Por ejemplo:)

struct header { size_t len; unsigned char data[]; };


Es un "hecho" aceptado que usar goto es una práctica de ingeniería de software deficiente. Eso no lo hace verdad. Hay momentos en que goto es útil, particularmente cuando se maneja la limpieza y cuando se transfiere desde el ensamblador.

Los miembros flexibles de la matriz me parecen tener un uso principal, que es mapear formatos de datos heredados como formatos de plantillas de ventana en RiscOS. Habrían sido sumamente útiles para esto hace unos 15 años, y estoy seguro de que aún hay personas que se ocupan de cosas que les resultarían útiles.

Si el uso de miembros flexibles de la matriz es una mala práctica, entonces sugiero que todos vayamos a decirles a los autores de la especificación C99 esto. Sospecho que podrían tener una respuesta diferente.


Hay algunas desventajas relacionadas con la forma en que se usan a veces las estructuras, y puede ser peligroso si no piensas en las implicaciones.

Para su ejemplo, si inicia una función:

void test(void) { struct header; char *p = &header.data[0]; ... }

Luego, los resultados no están definidos (ya que nunca se asignó almacenamiento a los datos). Esto es algo de lo que normalmente sabrá, pero hay casos donde los programadores de C probablemente estén acostumbrados a poder usar la semántica de valores para las estructuras, que se descompone de otras maneras.

Por ejemplo, si defino:

struct header2 { int len; char data[MAXLEN]; /* MAXLEN some appropriately large number */ }

Entonces puedo copiar dos instancias simplemente por asignación, es decir:

struct header2 inst1 = inst2;

O si se definen como punteros:

struct header2 *inst1 = *inst2;

Sin embargo, esto no funcionará, ya que los data matriz variable no se copian. Lo que quiere es malloc dinámicamente el tamaño de la estructura y copiar sobre la matriz con memcpy o equivalente.

Del mismo modo, escribir una función que acepte una estructura no funcionará, ya que los argumentos en las llamadas a funciones son, de nuevo, copiados por valor y, por lo tanto, lo que obtendrá probablemente sea solo el primer elemento de los data de su matriz.

Esto no es una mala idea, pero debe tener en cuenta que siempre debe asignar dinámicamente estas estructuras y solo pasarlas como punteros.


He visto algo como esto: desde la interfaz C y la implementación.

struct header { size_t len; unsigned char *data; }; struct header *p; p = malloc(sizeof(*p) + len + 1 ); p->data = (unsigned char*) (p + 1 ); // memory after p is mine!

Nota: los datos no necesitan ser el último miembro.


La razón por la que daría por no hacerlo es porque no vale la pena vincular su código a C99 solo para usar esta función.

El punto es que siempre puedes usar el siguiente modismo:

struct header { size_t len; unsigned char data[1]; };

Eso es completamente portátil. Luego puede tener en cuenta el 1 al asignar la memoria para n elementos en la matriz de data :

ptr = malloc(sizeof(struct header) + (n-1));

Si ya tiene C99 como requisito para compilar su código por cualquier otra razón o si tiene como objetivo un compilador específico, no veo ningún daño.


No, usar miembros de matriz flexible en C no es una mala práctica.

Esta característica del lenguaje se estandarizó por primera vez en ISO C99, 6.7.2.1 (16). Para el estándar actual, ISO C11, se especifica en la Sección 6.7.2.1 (18).

Puedes usarlos así:

struct Header { size_t d; long v[]; }; typedef struct Header Header; size_t n = 123; // can dynamically change during program execution // ... Header *h = malloc(sizeof(Header) + sizeof(long[n])); h->n = n;

Alternativamente, puede asignarlo así:

Header *h = malloc(sizeof *h + n * sizeof h->v[0]);

Tenga en cuenta que sizeof(Header) incluye eventuales bytes de relleno, por lo tanto, la siguiente asignación es incorrecta y puede producir un desbordamiento del búfer:

Header *h = malloc(sizeof(size_t) + sizeof(long[n])); // invalid!

Una estructura con un conjunto flexible de miembros reduce el número de asignaciones en 1/2, es decir, en lugar de 2 asignaciones para un objeto struct, solo necesita 1. Por lo tanto, si debe asignar un gran número de instancias de estructura, puede mejorar de forma considerable el tiempo de ejecución de su programa (por un factor constante).

En contraste con eso, el uso de construcciones no estandarizadas para miembros de matriz flexibles que producen un comportamiento indefinido (por ejemplo, como en long v[0]; o long v[1]; ) obviamente es una mala práctica. Por lo tanto, como cualquier comportamiento indefinido, esto debería evitarse.

Desde que se lanzó el ISO C99 en 1999, hace casi 20 años, luchar por la compatibilidad con ISO C89 es un argumento débil.


Te referías...

struct header { size_t len; unsigned char data[]; };

En C, esa es una expresión común. Creo que muchos compiladores también aceptan:

unsigned char data[0];

Sí, es peligroso, pero una vez más, no es más peligroso que las matrices C normales, es decir, MUY peligroso ;-). Úselo con cuidado y solo en circunstancias en las que realmente necesite una matriz de tamaño desconocido. Asegúrese de malloc y liberar la memoria correctamente, usando algo como: -

foo = malloc(sizeof(header) + N * sizeof(data[0])); foo->len = N;

Una alternativa es hacer que los datos solo sean un puntero a los elementos. Luego puede realloc () datos al tamaño correcto según sea necesario.

struct header { size_t len; unsigned char *data; };

Por supuesto, si preguntara sobre C ++, cualquiera de estos sería una mala práctica. Entonces normalmente usarías vectores STL.