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

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

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

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

Ответ 1

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

Ответ 2

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

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

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

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

ИЗМЕНИТЬ

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

Ответ 3

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

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

Ответ 4

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

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

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

Правильный способ сделать "C struct hack" (совместимый с диалоги C, восходящий к 1989 ANSI C и, вероятно, намного раньше) состоит в том, чтобы использовать совершенно допустимый массив размером 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 в интерфейсе обмена сообщениями с общей памятью между ядром и пользовательским пространством. Мне не нужны указатели, потому что они были бы значимыми только для исходного адресного пространства процесса, генерирующего сообщение. Ядро части программного обеспечения имело представление о памяти, используя его собственное сопоставление на другом адресе, и поэтому все было основано на расчетах смещения.

Ответ 5

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

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