2008-11-17 3 views
48

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

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

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

+3

Это «структурный взлом», описанный в вопросе 2.6 [comp.lang.c FAQ] (http://www.c-faq.com/). Деннис Ритчи назвал это «необоснованным chumminess с реализацией C». C99 представила новую языковую функцию - «гибкий элемент массива», чтобы заменить структурный хак. Даже компилятор Microsoft, который отличается отсутствием поддержки C99, поддерживает гибкие элементы массива. – 2012-09-11 18:37:11

+0

НЕ добавляйте тег `c` к этому вопросу.Правила C++ для этого сильно отличаются от правил C. – 2014-05-21 06:03:52

+0

@BenVoigt Принятый ответ - это чистый код C, поэтому, я думаю, ваше редактирование неверно. c hack применяется как для c, так и для C++ таким же образом – 2014-05-21 06:14:23

ответ

33

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

22

Это старый C взломать, чтобы позволить гибкие массивы размера.

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

+3

К сожалению, Visual Studio очень бедна, когда дело доходит до поддержки C99. :( – 2008-11-17 07:04:50

23

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

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

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

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

EDIT

Примечание: Даже если пост Раймонда говорит 0 длина массивов законны в C99 на самом деле они до сих пор не разрешены в C99. Вместо массива длиной 0 здесь вы должны использовать массив длиной 1

8

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

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

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

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

0

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

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

Смежные вопросы