Использование членов гибкого массива в C плохая практика? - PullRequest
69 голосов
/ 29 октября 2008

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

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

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

Ответы [ 7 ]

24 голосов
/ 29 октября 2008

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

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

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

19 голосов
/ 29 октября 2008

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

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

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

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

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

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

10 голосов
/ 29 октября 2008

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

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;

Альтернатива - сделать данные просто указателями на элементы. Затем вы можете перераспределить данные () к нужному размеру в соответствии с требованиями.

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

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

6 голосов
/ 16 сентября 2017

Нет, использование гибких элементов массива в 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 голосов
/ 01 июня 2010

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

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

4 голосов
/ 03 марта 2014

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

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

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.

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

4 голосов
/ 20 мая 2009

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