Какая потребность в массиве с нулевыми элементами?

В коде ядра Linux я нашел следующее, чего я не могу понять.

struct bts_action { u16 type; u16 size; u8 data[0]; } __attribute__ ((packed)); 

Код находится здесь: http://lxr.free-electrons.com/source/include/linux/ti_wilink_st.h

Какова необходимость и цель массива данных с нулевыми элементами?

Это способ иметь переменные размеры данных без необходимости одновременного вызова malloc ( kmalloc в этом случае). Вы бы использовали его так:

 struct bts_action *var = kmalloc(sizeof(*var) + extra, GFP_KERNEL); 

Раньше это не было стандартным и считалось взломом (как сказал Аникет), но он был стандартизован на C99 . Стандартный формат для него теперь:

 struct bts_action { u16 type; u16 size; u8 data[]; } __attribute__ ((packed)); /* Note: the __attribute__ is irrelevant here */ 

Обратите внимание, что вы не указываете размер поля data . Также обратите внимание, что эта специальная переменная может быть только в конце структуры.


В C99 этот вопрос объясняется в 6.7.2.1.16 (акцент мой):

В качестве особого случая последний элемент структуры с более чем одним именованным элементом может иметь неполный тип массива; это называется гибким элементом массива . В большинстве ситуаций гибкий элемент массива игнорируется. В частности, размер структуры выглядит так, как если бы гибкий элемент массива был исключен, за исключением того, что он мог иметь более длинное дополнение, чем подразумевалось бы упущение. Однако, когда a. (или ->) имеет левый операнд, который является (указателем на) структуру с гибким членом массива, а правый операнд – этим членом, он ведет себя так, как если бы этот элемент был заменен самым длинным массивом (с тем же типом элемента ), который не сделает структуру больше, чем объект, к которому обращаются; смещение массива должно оставаться равным элементу гибкого элемента массива, даже если это будет отличаться от размера массива замены. Если этот массив не будет содержать никаких элементов, он будет вести себя так, как если бы у него был один элемент, но поведение не определено, если была предпринята попытка доступа к этому элементу или для создания указателя, который прошел мимо него.

Или, другими словами, если у вас есть:

 struct something { /* other variables */ char data[]; } struct something *var = malloc(sizeof(*var) + extra); 

Вы можете получить доступ к var->data с индексами в [0, extra) . Обратите внимание, что sizeof(struct something) будет давать только размер, учитывающий другие переменные, т. Е. Дает data размером 0.


Интересно также отметить, как стандарт фактически дает примеры построения такой конструкции (6.7.2.1.17):

 struct s { int n; double d[]; }; int m = /* some value */; struct s *p = malloc(sizeof (struct s) + sizeof (double [m])); 

Еще одно интересное примечание по стандарту в том же самом месте (внимание мое):

предполагая, что вызов malloc преуспевает, объект, на который указывает p, ведет себя для большинства целей, как если бы p был объявлен как:

 struct { int n; double d[m]; } *p; 

(есть обстоятельства, при которых эта эквивалентность нарушается, в частности, смещения члена d могут быть не одинаковыми ).

На самом деле это взлом для GCC ( C90 ).

Его также называют структурным взломом .

Поэтому в следующий раз я бы сказал:

 struct bts_action *bts = malloc(sizeof(struct bts_action) + sizeof(char)*100); 

Это будет эквивалентно:

 struct bts_action{ u16 type; u16 size; u8 data[100]; }; 

И я могу создать любое количество таких объектов структуры.

Идея состоит в том, чтобы разрешить массив переменных размеров в конце структуры. Предположительно, bts_action – это некоторый пакет данных с заголовком фиксированного размера (поля type и size ) и членом data переменным размером. Объявив его как массив длиной 0, он может быть проиндексирован так же, как и любой другой массив. Затем вы выделили бы структуру bts_action , например 1024-байтовый размер data , например:

 size_t size = 1024; struct bts_action* action = (struct bts_action*)malloc(sizeof(struct bts_action) + size); 

См. Также: http://c2.com/cgi/wiki?StructHack

Код недействителен C ( см. Это ). Ядро Linux по очевидным причинам не имеет ни малейшего отношения к переносимости, поэтому использует большое количество нестандартного кода.

То, что они делают, является нестандартным расширением GCC с размером массива 0. Стандартная совместимая программа могла бы написать u8 data[]; и это означало бы то же самое. Авторы ядра Linux, по-видимому, любят делать вещи бесполезно сложными и нестандартными, если есть возможность сделать это.

В более старых стандартах C завершение структуры с пустым массивом называлось «struct hack». Другие уже объяснили свою цель в других ответах. Структурный взлом в стандарте C90 был неопределенным поведением и мог вызвать сбои, главным образом, поскольку компилятор C может добавлять любое количество байтов заполнения в конце структуры. Такие байты заполнения могут сталкиваться с данными, которые вы пытались «взломать» в конце структуры.

GCC на ранней стадии сделал нестандартное расширение, чтобы изменить это от неопределенного до четко определенного поведения. Таким образом, стандарт C99 адаптировал эту концепцию, и любая современная программа C может поэтому использовать эту функцию без риска. Он известен как гибкий элемент массива в C99 / C11.

Другое не так часто рассматриваемое использование массива нулевой длины – это получить именованный ярлык внутри структуры.

Предположим, что у вас есть несколько больших определений структуры (охватывает несколько строк кеша), которые вы хотите удостовериться, что они выровнены по границе линии кэша как в начале, так и посередине, где она пересекает границу.

 struct example_large_s { u32 first; // align to CL u32 data; .... u64 *second; // align to second CL after the first one .... }; 

В коде вы можете объявить их с помощью расширений GCC, например:

 __attribute__((aligned(CACHE_LINE_BYTES))) 

Но вы все равно хотите убедиться, что это принудительно выполняется во время выполнения.

 ASSERT (offsetof (example_large_s, first) == 0); ASSERT (offsetof (example_large_s, second) == CACHE_LINE_BYTES); 

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

 assert (offsetof (one_struct, ) == 0); assert (offsetof (one_struct, ) == CACHE_LINE_BYTES); assert (offsetof (another_struct, ) == 0); assert (offsetof (another_struct, ) == CACHE_LINE_BYTES); 

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

 #define CACHE_LINE_ALIGN_MARK(mark) u8 mark[0] __attribute__((aligned(CACHE_LINE_BYTES))) struct example_large_s { CACHE_LINE_ALIGN_MARK (cacheline0); u32 first; // align to CL u32 data; .... CACHE_LINE_ALIGN_MARK (cacheline1); u64 *second; // align to second CL after the first one .... }; 

Тогда код подтверждения выполнения будет намного проще поддерживать:

 assert (offsetof (one_struct, cacheline0) == 0); assert (offsetof (one_struct, cacheline1) == CACHE_LINE_BYTES); assert (offsetof (another_struct, cacheline0) == 0); assert (offsetof (another_struct, cacheline1) == CACHE_LINE_BYTES); 
  • Выполнение case-statement в структуре агрегации mongodb
  • $ lookup для ObjectId's в массиве
  • Структура объекта C ++ в памяти Vs a Struct
  • Что такое оператор разматывания в MongoDB?
  • Соответствие ObjectId для String для $ graphLookup
  • массивы нулевой длины
  • Наследование структуры в C
  • Возвращать только согласованные элементы субдокумента внутри вложенного массива
  • Итерация через участников Struct и Class
  • Как суммировать сумму в MongoDB, чтобы получить общий счет?
  • Можно ли удалить ребенка из коллекции и решить проблемы в SaveChanges?
  • Давайте будем гением компьютера.