2010-07-25 18 views
8

Esto es como struct hack. ¿Es válido de acuerdo con la norma C?¿Es este truco válido según la norma?

// error check omitted! 

    typedef struct foo { 
     void *data; 
     char *comment; 
     size_t num_foo; 
    }foo; 

    foo *new_Foo(size_t num, blah blah) 
    { 
     foo *f; 
     f = malloc(num + sizeof(foo) + MAX_COMMENT_SIZE); 
     f->data = f + 1;   // is this OK? 
     f->comment = f + 1 + num; 
     f->num_foo = num; 
     ... 
     return f; 

} 

Respuesta

6

Sí, es completamente válido. Y le recomiendo encarecidamente que lo haga cuando le permite evitar asignaciones adicionales innecesarias (y el manejo de errores y la fragmentación de la memoria que implican). Otros pueden tener opiniones diferentes.

Por cierto, si los datos no es void * pero algo que se puede acceder directamente, es aún más fácil (y más eficiente, ya que ahorra espacio y evita el direccionamiento indirecto adicional) para declarar su estructura como:

struct foo { 
    size_t num_foo; 
    type data[]; 
}; 

y asigne espacio para la cantidad de datos que necesita. La sintaxis [] solo es válida en C99, por lo tanto, para compatibilidad con C89, debe usar [1], pero esto puede desperdiciar algunos bytes.

+0

Como usted sabe pero probablemente valga la pena señalar, el miembro flexible de la matriz al final de la estructura solo funciona cuando tiene un elemento de longitud variable. La pregunta tiene dos elementos de longitud variable ('datos' y' comentario') y no puede usar fácilmente la técnica. –

+0

En ese caso, almacene el 'comentario' como una cadena' malloc() 'normal asignada. –

+0

FWIW, di esta respuesta antes de agregar el campo de comentarios. Con un comentario agregado, siga el consejo de Donal. –

1

Ese es un viejo juego, aunque la forma habitual es como

struct foo { 
    size_t size 
    char data[1] 
} 

y luego asignar el espacio tan grande como desee y utilizar matriz como si tuviera el tamaño deseado.

Es es válida, pero le recomiendo que encuentre otra manera si es posible: hay muchas posibilidades de arruinar esto.

+0

Este viejo truco no es * válido * (estrictamente) de acuerdo con la norma. – Nyan

+0

No, no lo es, pero * funcionará * con cualquier implementación conforme de 'malloc'. :: piensa un poco sobre las arquitecturas de memoria segmentadas patológicamente :: * Casi * cualquier 'malloc' conforme. – dmckee

+1

Es válido como consecuencia de otros requisitos de la norma, por ejemplo, el requisito de que si una estructura coincide con la parte inicial de otra, el acceso a esos elementos a través de la 2 es compatible. C99 lo hace explícitamente compatible con la notación de matriz flexible '[]' pero el antiguo '[1]' método ** necesariamente ** también funcionará. –

1

Sí, la idea general de la hack es válido, pero al menos como lo leí, no se han puesto en práctica con razón. Esta cantidad que hemos hecho a la derecha:

f = malloc(num + sizeof(foo) + MAX_COMMENT_SIZE); 
    f->data = f + 1;   // is this OK? 

Pero esto es incorrecto:

f->comment = f + 1 + num; 

Desde f es foo *, la f+1+num se calcula en términos de sizeof(foo) - es decir, que es equivalente a decir f[1+num] - - Intenta indexar al 1+numthfoo en una matriz. Estoy bastante seguro de que es no lo que quiere. Cuando asigna los datos, está pasando sizeof(foo)+num+MAX_COMMENT_SIZE, entonces lo que está asignando espacio es num char s, y lo que (presumiblemente) desea es señalar f->comment a un punto en la memoria que es num char s después de f->data, que sería más de esta manera:

f->comment = (char *)f + sizeof(foo) + num; 

casting f a un char * fuerza la matemáticas por hacer en términos de char s en lugar de foo s.

otoh, ya que siempre está asignando MAX_COMMENT_SIZE para comment, probablemente simplificar las cosas (bastante) un poco, y usar algo como esto:

typedef struct foo { 
    char comment[MAX_COMMENT_SIZE]; 
    size_t num_foo; 
    char data[1]; 
}foo; 

Y luego asignarlo como:

foo *f = malloc(sizeof(foo) + num-1); 
f->num_foo = num; 

y que va a trabajar sin ningún tipo de manipulación de punteros en absoluto.Si usted tiene un compilador C99, puede modificar esta ligeramente:

typedef struct foo { 
    char comment[MAX_COMMENT_SIZE]; 
    size_t num_foo; 
    char data[]; 
}foo; 

y asignar:

foo *f = malloc(sizeof(foo) + num); 
f->num_foo = num; 

Esto tiene la ventaja adicional de que el estándar de hecho lo bendice, aunque en este caso la ventaja es bastante minor (creo que la versión con funcionará con cada compilador C89/90 existente).

+0

Has detectado esto casi al mismo tiempo que yo: compusimos nuestras respuestas en paralelo. –

+0

@Jonathan Leffler: Sí, eso es casi una respuesta tan detallada como la mía, eso es casi inevitable ... –

4

La línea que cuestiona es válida, como han dicho otros.

Curiosamente, la siguiente línea, que no ha consultado, es sintácticamente válida pero no le proporciona la respuesta que desea (excepto en el caso en que num == 0).

typedef struct foo 
{ 
    void *data; 
    char *comment; 
    size_t num_foo; 
} foo; 

foo *new_Foo(size_t num, blah blah) 
{ 
    foo *f; 
    f = malloc(num + sizeof(foo) + MAX_COMMENT_SIZE); 
    f->data = f + 1;   // This is OK 
    f->comment = f + 1 + num; // This is !!BAD!! 
    f->num_foo = num; 
    ... 
    return f; 
} 

El valor de f + 1 es un foo * (implícitamente coaccionado en un void * por la asignación).

El valor de f + 1 + num es también un foo *; apunta al num+1thfoo.

Lo que probablemente tenía en mente era:

foo->comment = (char *)f->data + num; 

O:

foo->comment = (char *)(f + 1) + num; 

Nótese que si bien GCC permitirá añadir num a un puntero nulo, y este será tratado como si sizeof(void) == 1, el estándar C no le da ese permiso.

0

Otro posible problema podría ser la alineación.

Si simplemente malloc su f->data, puede de forma segura, p. convierta su void* en double* y úselo para leer/escribir un doble (siempre que num sea lo suficientemente grande). Sin embargo, en su ejemplo ya no puede hacer eso, ya que los datos f-> pueden no estar alineados correctamente. Por ejemplo, para almacenar un doble en f-> datos, necesitarás usar algo como memcpy en lugar de un simple encasillado.

+0

Usando mi enfoque (matriz al final) resuelve el problema de alineación automáticamente. También se puede resolver asignando suficiente espacio extra como para redondear 'f-> data' al siguiente múltiplo de' sizeof (double) 'o el tipo que necesite. –

0

Prefiero usar alguna función para asignar los datos dinámicamente y liberarlos correctamente en su lugar.

El uso de este truco solo le ahorra la molestia de inicializar la estructura de datos y puede provocar problemas muy graves (consulte el comentario de Jerry).

me gustaría hacer algo como esto:

typedef struct foo { 
     void *data; 
     char *comment; 
     size_t num_foo; 
    }foo; 
foo *alloc_foo(void * data, size_t data_size, const char *comment) 
{ 
    foo *elem = calloc(1,sizeof(foo)); 
    void *elem_data = calloc(data_size, sizeof(char)); 
    char *elem_comment = calloc(strlen(comment)+1, sizeof(char)); 
    elem->data = elem_data; 
    elem->comment = elem_comment; 

    memcpy(elem_data, data, data_size); 
    memcpy(elem_comment, comment, strlen(comment)+1); 
    elem->num_foo = data_size + strlen(comment) + 1; 
} 

void free_foo(foo *f) 
{ 
    if(f->data) 
     free(f->data); 
    if(f->comment) 
     free(f->comment); 
    free(f); 
} 

Tenga en cuenta que lo hice sin verificación de validez de los datos, y mi alloc se puede optimizar (en sustitución de strlen() llama por un valor de longitud almacenado).

Me parece que este comportamiento es más seguro ... al precio de un trozo de datos diseminados tal vez.

Cuestiones relacionadas