c arrays language-lawyer undefined-behavior flexible-array-member

¿Puedo "sobre-extender" una matriz asignando más espacio a la estructura adjunta?



arrays language-lawyer (2)

Francamente, ¿este código es válido o produce UB?

#include <stdio.h> #include <stdlib.h> #include <string.h> struct __attribute__((__packed__)) weird_struct { int some; unsigned char value[1]; }; int main(void) { unsigned char text[] = "Allie has a cat"; struct weird_struct *ws = malloc(sizeof(struct weird_struct) + sizeof(text) - 1); ws->some = 5; strcpy(ws->value, text); printf("some = %d, value = %s/n", ws->some, ws->value); free(ws); return 0; }

http://ideone.com/lpByQD

Nunca pensaría que es válido para algo como esto, pero parece que las colas de mensajes de SystemV hacen exactamente eso: consulte la página del manual .

Entonces, si las colas de SysV msg pueden hacer eso, ¿quizás yo también pueda hacer esto? Creo que esto me resultaría útil para enviar datos a través de la red (de ahí el __attribute__((__packed__)) ).

O, tal vez esto sea una garantía específica de las colas de mensajes SysV y no debería hacer algo así en otro lugar. O, tal vez esta técnica puede ser empleada, solo que lo hago mal? Me di cuenta de que sería mejor preguntar.

Esto - 1 en malloc(sizeof(struct weird_struct) + sizeof(text) - 1) se debe a que tengo en cuenta que de todos modos se asigna un byte gracias al unsigned char value[1] por lo que puedo restarlo de sizeof(text) .


El Estándar permite que las implementaciones actúen de la forma que consideren adecuada si el código accede a un objeto de matriz más allá de sus límites establecidos, incluso si el código posee el almacenamiento al que se accederá de ese modo. Por lo que puedo decir, esta regla pretende permitir que un compilador tenga algo como:

struct s1 { char arr[4]; char y; } *p; int x; ... p->y = 1; p->arr[x] = 2; return p->y;

Tratarlo como equivalente a:

struct s1 { char arr[4]; char y; } *p; int x; ... p->arr[x] = 2; p->y = 1; return 1;

evitar un paso de carga adicional, sin tener que permitir pesimista la posibilidad de que x pueda ser igual a 4. Los compiladores de calidad deben ser capaces de reconocer ciertas construcciones que implican acceder a matrices más allá de sus límites establecidos (por ejemplo, aquellos que involucran un puntero a una estructura con un solo La matriz de elementos como su último elemento) y manejarlos con sensatez, pero nada en la Norma requeriría que lo hicieran, y algunos escritores de compiladores adoptan la actitud de que el permiso para que los compiladores se comporten de una manera sin sentido debe interpretarse como una invitación a hacerlo. Creo que ese comportamiento se definiría, incluso para el caso x==4 (lo que significa que el compilador tendría la posibilidad de modificar y ), si la escritura de la matriz se manejara a través de algo como: (char*)(struct s1*)(p->arr)[x] = 2; pero el Estándar no está realmente claro si es necesario el lanzamiento a struct s1* .


La forma estándar de C ( desde C99 ) para hacer esto sería utilizar un miembro de matriz flexible . El último miembro de la estructura debe ser un tipo de matriz incompleta y puede asignar la cantidad necesaria de memoria en tiempo de ejecución.

Algo como

struct __attribute__((__packed__)) weird_struct { int some; unsigned char value [ ]; //nothing, no 0, no 1, no nothing... };

y después

struct weird_struct *ws = malloc(sizeof(struct weird_struct) + strlen("this to be copied") + 1);

o

struct weird_struct *ws = malloc(sizeof(struct weird_struct) + sizeof("this to be copied"));

hará el trabajo

Relacionado, citando la norma C11 , capítulo §6.7.2.1

Como caso especial, el último elemento de una estructura con más de un miembro nombrado puede tener un tipo de matriz incompleta; Esto se llama un miembro de matriz flexible . En la mayoría de las situaciones, el miembro de la matriz flexible se ignora. En particular, el tamaño de la estructura es como si se hubiera omitido el miembro de la matriz flexible, excepto que puede tener más relleno posterior del que implicaría la omisión. Sin embargo, cuando un . (o -> ) el operador tiene un operando izquierdo que es (un puntero a) una estructura con un miembro de matriz flexible y el operando derecho nombra a ese miembro, se comporta como si ese miembro hubiera sido reemplazado por 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 la matriz flexible, incluso si fuera diferente de la matriz de reemplazo. Si esta matriz no tuviera 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 que lo supera.

Relacionado con el uso de la matriz de un elemento, de la página de manual de gcc en línea para la opción de compatibilidad de matriz de longitud cero

struct line { int length; char contents[0]; }; struct line *thisline = (struct line *) malloc (sizeof (struct line) + this_length); thisline->length = this_length;

En ISO C90, deberías dar a los contents una longitud de 1, lo que significa que desperdicias espacio o complicas el argumento a malloc.

que también responde a la parte -1 en el argumento malloc() , ya que se garantiza que sizeof(char) es 1 en C.