2008-10-29 12 views
58

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?¿Está utilizando miembros de matriz flexibles en C mala práctica?

(Flexible array members son una característica C introducido en C99 mediante el cual uno puede declarar el último elemento a ser una matriz de tamaño no especificado Por ejemplo:.)

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

Respuesta

9

Usted decir ...

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, de nuevo, en realidad no es más peligroso que las matrices normales C - 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 que malloc y libera la memoria correctamente, usando algo como: -

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

Una alternativa es hacer que los datos sean simplemente 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 estuviera preguntando acerca de C++, cualquiera de estos sería una mala práctica. Entonces normalmente usarías vectores STL.

+2

siempre que esté codificando en un sistema donde STL es compatible. –

+5

C++ pero no STL ... ¡No es una idea agradable! – Roddy

+7

Nombre un compilador que acepte matrices de longitud cero. (Si la respuesta fue GCC, ahora nombre otra). No está sancionado por el estándar C. –

18

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 de la matriz flexible me parecen tener un uso principal, que es el mapeo de 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 usar una matriz flexible es una mala práctica, entonces sugiero que todos vayamos a decirle a los autores del C99 que lo especifiquen. Sospecho que podrían tener una respuesta diferente.

+13

goto también es útil cuando queremos implementar una implementación recursiva de un algoritmo usando una implementación no recursiva en aquellos casos en que la recursividad podría generar una sobrecarga adicional en el compilador. – pranavk

+0

@pranavk Probablemente deberías estar usando 'while', entonces. – YoYoYonnY

+0

La programación en red es otra, tiene el encabezado como estructura y el paquete (o lo que se llama en la capa en la que está) como matriz flexible. Llamando a la siguiente capa, tira del encabezado y pasa el paquete. Haga esto para cada capa en la pila de red. (En caso de que los datos de menor revivió de la capa inferior a struct para la capa que está en el Inn) – fhtuft

19

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 se puede utilizar el siguiente idioma:

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

Eso es totalmente portátil. A continuación, puede tomar la 1 a la hora de asignar la memoria para n elementos de la matriz data:

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

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

+0

La última línea debe ser ptr = malloc (sizeof (header) + n); donde n es la longitud de la cadena y utiliza el 1 como terminación \ 0. –

+3

Gracias. Dejé el n-1 ya que podría no usarse como una cadena. –

+1

uso no le importaría el signo si era seguro una cadena. Con respecto a esto, n-1 es correcto. – botismarius

3

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)); 

Configuración FLEXIBLE_SIZE a SIZE_MAX casi asegura esta fallará:

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

Es demasiado complejo y no se beneficia el uso de '[1]' para la compatibilidad con C89, incluso si es necesario ... –

6

He visto algo como esto: desde la interfaz C e 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 tienen que ser el último miembro.

+15

De hecho, esto tiene la ventaja de que 'datos' no tiene por qué ser el último miembro, sino que también implica una desreferencia adicional cada tiempo 'data' se usa. Los arreglos flexibles reemplazan esa desreferencia con un desplazamiento constante del puntero estructural principal, que es gratuito en algunas máquinas particularmente comunes y es barato en otros lugares. –

3

Existen algunas desventajas relacionadas con la forma en que se utilizan a veces las estructuras, y puede ser peligroso si no se piensa en las implicaciones.

Para su ejemplo, si se inicia una función:

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

    ... 
} 

A continuación, los resultados son indefinidos (ya que no se almacenen nunca fue asignado para 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 I definen:

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

entonces puedo copiar dos casos simplemente por la asignación, es decir:

struct header2 inst1 = inst2; 

O si están definidos como punteros:

struct header2 *inst1 = *inst2; 

Sin embargo, esto no funcionará, ya que no se copia la matriz variable data. Lo que desea es dinámicamente malloc 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 se copian por valor y es probable que lo que obtendrá probablemente sea el primer elemento de la matriz data.

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

0

No, usar flexible array members en C no es una mala práctica.

Esta característica de idioma 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).

Puede utilizarlos como esto:

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; 

Como alternativa, puede asignarlo como esto:

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

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

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

Una estructura con af Los miembros lexibles de la matriz reducen el número de asignaciones en 1/2, es decir, en lugar de 2 asignaciones para un objeto struct, solo necesitas 1. Por lo tanto, si tienes que asignar una gran cantidad de instancias struct, mejorarás de forma medible el tiempo de ejecución de tu 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 ISO C99 fue lanzado en 1999, hace casi 20 años, luchar por la compatibilidad con ISO C89 es un argumento débil.

Cuestiones relacionadas