Как инициализировать структуру с помощью гибкого члена массива

У меня следующая структура

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

Я провел базовый поиск в Интернете (но безуспешно) о том, как создать экземпляр и инициализировать структуру с помощью гибкого элемента массива без использования malloc().

Например: для обычных структур, таких как

struct a {
    int age; 
    int sex;
};

Мы можем создать экземпляр struct a и инициализировать его как

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

Но для структур с гибким массивом в нем (например, _person, как упоминалось выше), как мы можем создать экземпляр и инициализировать, как мы делаем это для обычного structures?

Это вообще возможно? Если да, то как передать размер массива во время инициализации и фактическое значение, которое нужно инициализировать?

(or)

Верно ли, что единственный способ создать структуру с гибким массивом - это использовать malloc(), как указано в спецификации C99 - 6.7.2.1 Structure and union specifiers - point #17 ?!


person Sangeeth Saravanaraj    schedule 31.12.2011    source источник
comment
вы не можете, структура должна иметь размер времени компиляции.   -  person Anycorn    schedule 31.12.2011
comment
@Anycorn: структуры с гибкими элементами массива имеют размер времени компиляции.   -  person CB Bailey    schedule 31.12.2011
comment
У GCC есть расширение, которое позволяет делать что-то вроде struct { size_t len; int data[]; } x = { 4, { 1, 2, 3, 4 } };, и оно будет работать, но оно не переносимо. Вы всегда можете изучить версию alloca для вашей платформы в поисках, возможно, более переносимого решения, но вам нужно будет убедиться, что все они ведут себя одинаково и имеют одинаковые особенности реализации.   -  person Chris Lutz    schedule 31.12.2011
comment
@Charles не в том смысле, который означает плакат - должен быть какой-то malloc или эквивалент.   -  person Anycorn    schedule 31.12.2011
comment
@ChrisLutz Как называется расширение и как его использовать? предложения / указатели пожалуйста! Спасибо!   -  person Sangeeth Saravanaraj    schedule 31.12.2011
comment
gcc.gnu.org/onlinedocs/gcc-4.1. 2 / gcc / Zero-Length.html По-видимому, он включен по умолчанию (при условии, что вы не используете -ansi или что-нибудь, что могло бы его отключить).   -  person Chris Lutz    schedule 31.12.2011
comment
Если кто-то хочет узнать больше о расширении GCC, для него также есть специальный вопрос на SO: Почему статическая инициализация гибкого элемента массива работать?   -  person RobertS supports Monica Cellio    schedule 09.07.2020


Ответы (3)


Нет, гибкие массивы всегда нужно размещать вручную. Но вы можете использовать calloc для инициализации гибкой части и составной литерал для инициализации фиксированной части. Я бы обернул это функцией распределения inline следующим образом:

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

Обратите внимание, что это оставляет проверку, удалось ли выделение вызывающей стороне.

Если вам не нужно поле size, как я включил его здесь, макроса будет достаточно. Только то, что было бы невозможно проверить возврат calloc перед выполнением memcpy. Во всех системах, которые я программировал до сих пор, это будет отменяться относительно хорошо. Обычно я считаю, что возврат malloc не имеет большого значения. , но мнения по этому поводу сильно разнятся.

Это могло бы (в этом особом случае) дать оптимизатору больше возможностей интегрировать код в окружение:

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

Изменить: случай, когда это может быть лучше, чем функция, - это когда A и S являются константами времени компиляции. В этом случае составной литерал, поскольку он const квалифицирован, может быть выделен статически, а его инициализация может быть выполнена во время компиляции. Кроме того, если в коде появятся несколько распределений с одинаковыми значениями, компилятору будет разрешено реализовать только одну единственную копию этого составного литерала.

person Jens Gustedt    schedule 31.12.2011
comment
Создание непроверенной копии результата calloc() опасно; если распределение не удается, вы получаете дамп ядра (или другое неопределенное поведение, которое вряд ли будет тем, что вы хотели). - person Jonathan Leffler; 31.12.2011
comment
@JonathanLeffler, верно, изменю. Я интегрирую это в функцию. Что касается marcro, я просто отсылаю к своей общей тираде о проверке возврата malloc. - person Jens Gustedt; 31.12.2011
comment
Ваше встроенное функциональное решение великолепно. У макроса нет никаких преимуществ перед ним, ИМХО. Он не читается, не проверяет возвращаемое значение calloc и не будет работать лучше. Макросы обычно не работают лучше, чем встроенные функции (иногда хуже - подумайте о передаче strlen () макросу, который оценивает его дважды). - person ugoren; 31.12.2011
comment
Квалифицированные составные литералы @ugoren, const являются особенными с точки зрения оптимизации. С ними разрешена дополнительная оптимизация, см. Мою правку. Вероятно, можно было бы попытаться объединить оба подхода, имея функцию, которая выполняет что-то вроде calloc_and_copy_or_fail. - person Jens Gustedt; 31.12.2011
comment
Хороший. Кстати, вместо person *ret = calloc(sizeof(person) + n, 1); более безопасная привычка - писать person *ret = calloc(sizeof(*ret) + (sizeof(ret->name[0]) * n), 1);. - person Todd Lehman; 11.07.2015
comment
Вы покрываете пример, если OP хочет инициализировать гибкий элемент массива (FAM) равным 0. Но как можно инициализировать FAM с любыми другими значениями отдельных элементов или как в этом случае (поскольку name предполагается, что хранит строка) строка? Я предполагаю, что это то, что на самом деле имел в виду OP, когда он сказал инициализировать FAM. - person RobertS supports Monica Cellio; 09.07.2020
comment
Также: Возможно ли, что можно установить размер FAM только по его списку инициализаторов? Как это возможно в массивах неизвестного размера вне структуры, например: int a[] = {1,2,3,4,5};. - person RobertS supports Monica Cellio; 09.07.2020

Есть несколько приемов, которые вы можете использовать. Это зависит от вашего конкретного приложения.

Если вы хотите инициализировать одну переменную, вы можете определить структуру правильного размера:

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

Проблема в том, что вам нужно использовать приведение указателя, чтобы интерпретировать переменную как "person" (например, "* (person *) (& your_variable)". Но вы можете использовать объединение, чтобы избежать этого:

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

Итак, your_var.p относится к типу "человек". Вы также можете использовать макрос для определения вашего инициализатора, поэтому вы можете написать строку только один раз:

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

Другая проблема в том, что этот трюк не подходит для создания массива «человек». Проблема с массивами в том, что все элементы должны иметь одинаковый размер. В этом случае безопаснее использовать const char * вместо char[]. Или используйте динамическое размещение;)

person Giuseppe Guerrini    schedule 31.12.2011

Тип структуры с гибким элементом массива можно рассматривать так, как если бы этот элемент гибкого массива был опущен, поэтому вы можете инициализировать структуру таким образом.

person p = { 10, 'x' };

Однако в гибком массиве нет выделенных элементов, и любая попытка получить доступ к члену гибкого массива или сформировать указатель на один за его концом является недопустимой. Единственный способ создать экземпляр структуры с гибким элементом массива, который фактически имеет элементы в этом массиве, - это динамически выделить для него память, например, с помощью malloc.

person CB Bailey    schedule 31.12.2011
comment
Существует расширение GCC, которое позволяет вам указывать гибкий член массива, используя тот же синтаксис, если вам это нравится. - person Chris Lutz; 31.12.2011