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

В коде ядра 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

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

Ответ 1

Это способ иметь переменные размеры данных без необходимости называть 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. (или → ) имеет левый операнд, который равен   (указатель на) структуру с гибким членом массива и именами правых операндов, которые    член, он ведет себя так, как если бы этот элемент был заменен самым длинным массивом (с тем же   тип элемента), который не сделает структуру больше, чем объект, к которому обращаются;  смещение массива должно оставаться равным элементу гибкого массива, даже если это будет отличаться   от матрицы замены. Если этот массив не будет содержать никаких элементов, он будет вести себя так, как будто  он имел один элемент, но поведение undefined, если предпринимаются попытки получить доступ к этому   элемент или создать указатель, который проходит мимо него.

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

struct something
{
    /* other variables */
    char data[];
}

struct something *var = malloc(sizeof(*var) + extra);

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


Интересно также отметить, как стандарт действительно дает примеры malloc такой конструкции (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 могут быть не одинаковыми).

Ответ 2

На самом деле это хак, для GCC (C90).

Он также называется struct hack.

Итак, в следующий раз я бы сказал:

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

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

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

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

Ответ 3

Идея состоит в том, чтобы разрешить массив переменных размеров в конце структуры. Предположительно, 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

Ответ 4

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

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

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

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

Ответ 5

Еще одно не частое использование массива нулевой длины - это получить именованный ярлык внутри структуры.

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

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,     <name_of_first_member>) == 0);
assert (offsetof (one_struct,     <name_of_second_member>) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, <name_of_first_member>) == 0);
assert (offsetof (another_struct, <name_of_second_member>) == 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);