Cómo inicializar una estructura con un miembro de matriz flexible

6 minutos de lectura

tengo la siguiente estructura

typedef struct _person {
    int age;
    char sex;
    char name[];
}person;

Hice una búsqueda básica en Internet (pero sin éxito) sobre cómo crear una instancia e inicializar una estructura con un miembro de matriz flexible sin usar malloc().

Por ejemplo: para estructuras normales como

struct a {
    int age; 
    int sex;
};

Podemos crear una instancia de struct a e inicializarlo como

struct a p1 = {10, 'm'};

Pero para estructuras con matriz flexible (como _person como se mencionó anteriormente) ¿cómo podemos crear una instancia e inicializar como lo hacemos normalmente? structures?

¿Es posible? Si es así, ¿cómo pasamos el tamaño de la matriz durante la inicialización y el valor real que se inicializará?

(o)

¿Es cierto que la única forma de crear una estructura con una matriz flexible es usando malloc() como se menciona en la especificación C99 – 6.7.2.1 Structure and union specifiers - point #17?!

  • no puede, la estructura debe tener un tamaño de tiempo de compilación.

    – Cualquier grano

    31 de diciembre de 2011 a las 10:48

  • @Anycorn: las estructuras con miembros de matriz flexibles tienen un tamaño de tiempo de compilación.

    –CB Bailey

    31 de diciembre de 2011 a las 10:56

  • GCC tiene una extensión que te permite hacer algo como struct { size_t len; int data[]; } x = { 4, { 1, 2, 3, 4 } }; y funcionará, pero no es portátil. Siempre puede consultar la versión de su plataforma de alloca para una solución posiblemente más portátil, pero debe asegurarse de que todos se comporten de la misma manera y tengan las mismas peculiaridades de implementación.

    – Chris Lutz

    31 de diciembre de 2011 a las 10:58

  • @Charles no en el sentido que significa el cartel: debe haber algún tipo de malloc o equivalente.

    – Cualquier grano

    31 de diciembre de 2011 a las 11:30

  • gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Zero-Length.html Parece estar activado de forma predeterminada (suponiendo que no esté utilizando -ansi o cualquier cosa que lo deshabilite).

    – Chris Lutz

    31 de diciembre de 2011 a las 12:22

avatar de usuario
Jens Gustedt

No, los arreglos flexibles siempre deben asignarse manualmente. Pero puedes usar calloc para inicializar la parte flexible y un literal compuesto para inicializar la parte fija. Envolvería eso en una asignación inline funcionar así:

typedef struct person {
  unsigned age;
  char sex;
  size_t size;
  char name[];
} person;

inline
person* alloc_person(int a, char s, size_t n) {
  person * ret = calloc(sizeof(person) + n, 1);
  if (ret) memcpy(ret,
                  &(person const){ .age = a, .sex = s, .size = n},
                  sizeof(person));
  return ret;
}

Observe que esto deja el cheque si la asignación tuvo éxito a la persona que llama.

Si no necesitas un size como lo incluí aquí, incluso una macro sería suficiente. Sólo que no sería posible comprobar la devolución de calloc antes de hacer el memcpy. En todos los sistemas que programé hasta ahora, esto abortará relativamente bien. Generalmente pienso que retorno de malloc es de menor importanciapero las opiniones varían en gran medida sobre ese tema.

Esto quizás podría (en ese caso especial) dar más oportunidades al optimizador para integrar el código en el entorno:

#define ALLOC_PERSON(A,  S,  N)                                 \
((person*)memcpy(calloc(sizeof(person) + (N), 1),               \
                 &(person const){ .age = (A), .sex = (S) },     \
                 sizeof(person)))

Editar: El caso de que esto podría ser mejor que la función es cuando A y S son constantes de tiempo de compilación. En ese caso el literal compuesto, ya que es const calificado, podría asignarse estáticamente y su inicialización podría realizarse en tiempo de compilación. Además, si en el código aparecieran varias asignaciones con los mismos valores, al compilador se le permitiría realizar una única copia de ese literal compuesto.

  • Haciendo una copia no verificada del resultado de calloc() es peligroso; si la asignación falla, obtiene un volcado del núcleo (u otro comportamiento indefinido que es poco probable que sea lo que quería).

    –Jonathan Leffler

    31 de diciembre de 2011 a las 16:11

  • @JonathanLeffler, a la derecha, modificará. Integraré eso en la función. Para el marcro, solo me referiré a mi perorata genérica sobre verificar el retorno de malloc.

    – Jens Gusted

    31 de diciembre de 2011 a las 16:31

  • Su solución de función en línea es excelente. La macro no tiene ninguna ventaja sobre ella, en mi humilde opinión. Es ilegible, no verifica el valor de retorno de calloc y no funcionará mejor. Las macros generalmente no funcionan mejor que las funciones en línea (a veces peor: considere pasar strlen() a una macro, que la evalúa dos veces).

    – ugoren

    31 de diciembre de 2011 a las 17:38

  • @ugoren, const los literales compuestos calificados son especiales con respecto a la optimización. Se permite más optimización con ellos, vea mi edición. Probablemente se podría intentar combinar ambos enfoques al tener una función que haga algo como calloc_and_copy_or_fail.

    – Jens Gusted

    31 de diciembre de 2011 a las 17:52


  • Agradable. Por cierto, en lugar de person *ret = calloc(sizeof(person) + n, 1);un hábito más seguro es escribir person *ret = calloc(sizeof(*ret) + (sizeof(ret->name[0]) * n), 1);.

    –Todd Lehman

    11 de julio de 2015 a las 6:34


avatar de usuario
Giuseppe Guerrini

Hay algunos trucos que puedes usar. Depende de su aplicación particular.

Si desea inicializar una sola variable, puede definir una estructura del tamaño correcto:

   struct  {
        int age;
        char sex;
        char name[sizeof("THE_NAME")];
    } your_variable = { 55, 'M', "THE_NAME" };

El problema es que debe usar la conversión de punteros para interpretar la variable como “persona” (p. ej., “*(persona *)(&su_variable)”. Pero puede usar una unión contenedora para evitar esto:

union {
 struct { ..., char name[sizeof("THE_NAME")]; } x;
 person p;
} your_var = { 55, 'M', "THE_NAME" };

entonces, your_var.p es de tipo “persona”. También puede usar una macro para definir su inicializador, de modo que pueda escribir la cadena solo una vez:

#define INIVAR(x_, age_, sex_ ,s_) \
   union {\
     struct { ..., char name[sizeof(s_)]; } x;\
     person p;\
    } x_ = { (age_), (sex_), (s_) }

INIVAR(your_var, 55, 'M', "THE NAME");

Otro problema es que este truco no es adecuado para crear una matriz de “persona”. El problema con los arreglos es que todos los elementos deben tener el mismo tamaño. En este caso, es más seguro utilizar un const char * en lugar de un char[]. O usa la asignación dinámica 😉

Un tipo de estructura con un miembro de matriz flexible se puede tratar como si se hubiera omitido el miembro de matriz flexible, por lo que puede inicializar la estructura de esta manera.

person p = { 10, 'x' };

Sin embargo, no hay miembros de la matriz flexible asignados y cualquier intento de acceder a un miembro de la matriz flexible o formar un puntero a uno más allá de su final no es válido. La única forma de crear una instancia de una estructura con un miembro de matriz flexible que realmente tenga elementos en esta matriz es asignarle memoria dinámicamente, por ejemplo con malloc.

  • Hay una extensión GCC que le permite especificar el miembro de matriz flexible usando la misma sintaxis, si eso es lo que le gusta.

    – Chris Lutz

    31 de diciembre de 2011 a las 10:59

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad