Использует ли гибкие члены массива в C плохой практике?

Недавно я прочитал, что использование гибких членов массива в C было плохой практикой разработки программного обеспечения. Однако это утверждение не поддерживалось никакими аргументами. Это общепринятый факт?

(Элементы гибкого массива - это функция C, введенная на C99, в которой можно объявить последний элемент массивом неуказанного размера.:)

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

Ответ 1

Это признанный факт, что использование goto - это плохая практика разработки программного обеспечения. Это не так. Бывают случаи, когда goto полезен, особенно при обработке очистки и при переносе с ассемблера.

Элементы гибкого массива поражают меня тем, что я использую одно основное использование, с верхней части головы, которое отображает устаревшие форматы данных, такие как форматы шаблонов окон в RiscOS. Они были бы в высшей степени полезны для этого около 15 лет назад, и я уверен, что есть люди, которые там занимаются такими вещами, которые могли бы найти их полезными.

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

Ответ 2

ПОЖАЛУЙСТА, ВНИМАТЕЛЬНО ПРОЧТИТЕ КОММЕНТАРИИ НИЖЕ ОТВЕТА

По мере продвижения C-стандартизации больше нет причин использовать [1].

Причина, по которой я бы отказался сделать это, состоит в том, что не стоит привязывать ваш код к C99 только для того, чтобы использовать эту функцию.

Дело в том, что вы всегда можете использовать следующую идиому:

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

Это полностью переносимо. Тогда вы можете учесть 1 при выделении памяти для n элементов в массиве data:

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

Если у вас уже есть C99 в качестве требования для создания кода по любой другой причине или вы нацелены на определенный компилятор, я не вижу никакого вреда.

Ответ 3

Вы имели в виду...

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

В C это общая идиома. Я думаю, что многие компиляторы также принимают:

  unsigned char data[0];

Да, это опасно, но опять же, это действительно не более опасно, чем обычные C-массивы - то есть ОЧЕНЬ опасно;-). Используйте его с осторожностью и только в тех случаях, когда вам действительно нужен массив неизвестного размера. Убедитесь, что вы malloc и освободите память правильно, используя что-то вроде: -

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

Альтернативой является то, что данные просто являются указателями на элементы. Затем вы можете перевести данные realloc() в нужный размер.

  struct header
    {
     size_t len;
     unsigned char *data;
    }; 

Конечно, если бы вы спрашивали о С++, любой из них был бы плохой практикой. Затем вы обычно используете STL-векторы.

Ответ 4

Я видел что-то вроде этого: из интерфейса C и реализации.

  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! 

Примечание: данные не обязательно должны быть последним.

Ответ 5

Нет, использование членов гибкого массива в C не является плохой практикой.

Эта функция языка была впервые стандартизирована в ISO C99, 6.7.2.1 (16). Для действующего стандарта ISO C11 он указан в разделе 6.7.2.1 (18).

Вы можете использовать их так:

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;

Кроме того, вы можете выделить его следующим образом:

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

Обратите внимание, что sizeof(Header) включает возможные байты заполнения, таким образом, следующее распределение является неправильным и может привести к переполнению буфера:

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

Структура с элементами гибкого массива уменьшает количество выделений для него на 1/2, т.е. Вместо 2 выделений для одного объекта структуры вам нужно всего лишь 1. То есть меньше усилий и меньше памяти, занимаемой накладными расходами на распределение памяти. Кроме того, вы сохраняете хранилище для одного дополнительного указателя. Таким образом, если вам нужно выделить большое количество таких экземпляров структуры, вы заметно улучшите время выполнения и использование памяти вашей программой (с помощью постоянного фактора).

В отличие от этого, использование нестандартизированных конструкций для гибких элементов массива, которые приводят к неопределенному поведению (например, в long v[0]; или long v[1];), очевидно, является плохой практикой. Таким образом, как и любое неопределенное поведение, этого следует избегать.

С момента выпуска ISO C99 в 1999 году, почти 20 лет назад, стремление к совместимости с ISO C89 является слабым аргументом.

Ответ 6

В качестве дополнительной заметки, для совместимости с C89, такую ​​структуру следует распределять следующим образом:

struct header *my_header
  = malloc(offsetof(struct header, data) + n * sizeof my_header->data);

Или с помощью макросов:

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

Настройка FLEXIBLE_SIZE на SIZE_MAX почти гарантирует, что это не сработает:

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

Ответ 7

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

В вашем примере, если вы запустите функцию:

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

  ...
}

Тогда результаты undefined (поскольку для данных не было выделено хранилище). Это то, о чем вы, как правило, знаете, но есть случаи, когда программисты C, вероятно, используются, чтобы использовать семантику значений для структур, которая ломается различными способами.

Например, если я определяю:

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

Затем я могу скопировать два экземпляра просто путем назначения, то есть:

struct header2 inst1 = inst2;

Или, если они определены как указатели:

struct header2 *inst1 = *inst2;

Это, однако, не будет работать, поскольку массив переменных data не копируется. Вы хотите динамически распределять размер структуры и копировать по массиву с помощью memcpy или эквивалентного.

Аналогично, запись функции, которая принимает структуру, не будет работать, поскольку аргументы в вызовах функций снова копируются по значению, и, следовательно, то, что вы получите, вероятно, будет только первым элементом вашего массива data.

Это не делает его плохой идеей, но вы должны иметь в виду всегда динамически распределять эти структуры и передавать их только как указатели.