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

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

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

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

7 ответов

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

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

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

Нет, использование членов гибкого массива в 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 является слабым аргументом.

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

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

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

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

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

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

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

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

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

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

Конечно, если вы спрашиваете о C++, любой из них будет плохой практикой. Тогда вместо этого вы обычно используете векторы STL.

Я видел что-то вроде этого: из интерфейса 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! 

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

В качестве примечания, для совместимости с 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);

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

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

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

  ...
}

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

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

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

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

struct header2 inst1 = inst2;

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

struct header2 *inst1 = *inst2;

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

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

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

Другие вопросы по тегам