Массив нулевой длины - PullRequest
       40

Массив нулевой длины

50 голосов
/ 17 ноября 2008

Я работаю над рефакторингом некоторого старого кода и нашел несколько структур, содержащих массивы нулевой длины (ниже). Предупреждения подавлены прагмой, конечно, но я не смог создать "новые" структуры, содержащие такие структуры (ошибка 2233). Массив 'byData' используется в качестве указателя, но почему бы не использовать вместо него указатель? или массив длиной 1? И конечно, никаких комментариев не было добавлено, чтобы я получил удовольствие от процесса ... Есть ли причины использовать такую ​​вещь? Какой-нибудь совет в рефакторинге тех?

struct someData
{
   int nData;
   BYTE byData[0];
}

Примечание: это C ++, Windows XP, VS 2003

Ответы [ 5 ]

34 голосов
/ 17 ноября 2008

Да, это C-Hack.
Чтобы создать массив любой длины:

struct someData* mallocSomeData(int size)
{
    struct someData*  result = (struct someData*)malloc(sizeof(struct someData) + size * sizeof(BYTE));
    if (result)
    {    result->nData = size;
    }
    return result;
}

Теперь у вас есть объект someData с массивом указанной длины.

24 голосов
/ 17 ноября 2008

К сожалению, есть несколько причин, по которым вы бы объявили массив нулевой длины в конце структуры. По сути, это дает вам возможность иметь структуру переменной длины, возвращаемую из API.

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

Заметьте, что в его посте речь идет о массивах размера 1 вместо 0. Это так, потому что массивы нулевой длины - более поздняя запись в стандартах. Его пост все равно должен относиться к вашей проблеме. .

http://blogs.msdn.com/oldnewthing/archive/2004/08/26/220873.aspx

EDIT

Примечание. Несмотря на то, что в сообщении Рэймонда говорится, что массивы нулевой длины допустимы в C99, они фактически недопустимы в C99. Вместо массива длины 0 здесь вы должны использовать массив длины 1

22 голосов
/ 17 ноября 2008

Это старый C-хак, позволяющий использовать массивы гибких размеров.

В стандарте C99 это не обязательно, так как он поддерживает синтаксис arr [].

8 голосов
/ 21 мая 2014

Ваша интуиция о том, «почему бы не использовать массив размером 1», очень точна.

Код неправильно выполняет "взлом структуры C", поскольку объявления массивов нулевой длины являются нарушением ограничения. Это означает, что компилятор может сразу же отклонить ваш хак во время компиляции с помощью диагностического сообщения, которое останавливает перевод.

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

Правильный способ «взлома структуры C» (который совместим с диалектами C, начиная с ANSI C 1989 года и, возможно, намного раньше), заключается в использовании совершенно корректного массива размера 1:

struct someData
{
   int nData;
   unsigned char byData[1];
}

Кроме того, вместо sizeof struct someData размер детали перед byData рассчитывается по формуле:

offsetof(struct someData, byData);

Чтобы выделить struct someData с пробелом для 42 байтов в byData, мы бы тогда использовали:

struct someData *psd = (struct someData *) malloc(offsetof(struct someData, byData) + 42);

Обратите внимание, что это offsetof вычисление на самом деле является правильным вычислением даже в случае нулевого размера массива. Вы видите, sizeof вся структура может включать отступы. Например, если у нас есть что-то вроде этого:

struct hack {
  unsigned long ul;
  char c;
  char foo[0]; /* assuming our compiler accepts this nonsense */
};

Размер struct hack вполне может быть дополнен для выравнивания из-за элемента ul. Если unsigned long имеет ширину в четыре байта, то вполне возможно, sizeof (struct hack) равен 8, тогда как offsetof(struct hack, foo) почти наверняка 5. Метод offsetof - это способ получить точный размер предыдущей части структуры непосредственно перед массив.

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

Почему бы не использовать указатель? Потому что указатель занимает дополнительное место и должен быть инициализирован.

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

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

1 голос
/ 09 февраля 2018

Стоит указать IMO лучший способ для расчета размера, который используется в статье Раймонда Чена, ссылка на которую приведена выше.

struct foo
{
    size_t count;
    int data[1];
}

size_t foo_size_from_count(size_t count)
{
    return offsetof(foo, data[count]);
}

Смещение первой записи вне конца желаемого выделения также является размером желаемого выделения. ИМО - это чрезвычайно элегантный способ расчета размера. Не имеет значения, какой тип элемента массива переменного размера. Смещение (или FIELD_OFFSET или UFIELD_OFFSET в Windows) всегда записывается одинаково. Нет выражений sizeof (), которые бы случайно запутались.

...