it-swarm.com.ru

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

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

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

NB Это C++, Windows XP, VS 2003

50
bgee

Да, это 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 с массивом указанной длины.

34
Martin York

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

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

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

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

РЕДАКТИРОВАТЬ

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

24
JaredPar

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

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

22
arul

Ваша интуиция о том, "почему бы не использовать массив размером 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 в интерфейсе передачи сообщений совместно используемой памяти между ядром и пользовательским пространством. Я не хотел указателей там, потому что они были бы значимы только для исходного адресного пространства процесса, генерирующего сообщение. Часть ядра программного обеспечения имела вид на память, используя свое собственное отображение по другому адресу, и поэтому все было основано на вычислениях смещения.

8
Kaz

Стоит указать 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 (), чтобы случайно испортить.

1
Mike Ruete