Qual é a necessidade de matriz com elementos zero?

No código do kernel do Linux, encontrei a seguinte coisa que não consigo entender.

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

O código está aqui: http://lxr.free-electrons.com/source/include/linux/ti_wilink_st.h

Qual é a necessidade e o propósito de uma matriz de dados com elementos zero?

    Esta é uma maneira de ter tamanhos variables ​​de dados, sem ter que chamar malloc ( kmalloc neste caso) duas vezes. Você usaria assim:

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

    Isso costumava não ser padrão e foi considerado um hack (como Aniket disse), mas foi padronizado em C99 . O formato padrão para isso agora é:

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

    Observe que você não menciona nenhum tamanho para o campo de data . Note também que esta variável especial só pode vir no final da estrutura.


    Em C99, este assunto é explicado em 6.7.2.1.16 (ênfase minha):

    Como um caso especial, o último elemento de uma estrutura com mais de um membro nomeado pode ter um tipo de matriz incompleta; isso é chamado de membro da matriz flexível . Na maioria das situações, o membro da matriz flexível é ignorado. Em particular, o tamanho da estrutura é como se o membro da matriz flexível fosse omitido, exceto que pode ter mais preenchimento à direita do que a omissão implicaria. No entanto, quando a. (ou ->) operador tem um operando à esquerda que é (um ponteiro para) uma estrutura com um membro de matriz flexível eo operando à direita nomeia esse membro, ele se comporta como se esse membro fosse substituído pela matriz mais longa (com o mesmo tipo de elemento ) que não tornaria a estrutura maior que o object acessado; o deslocamento do array deve permanecer igual ao do array flexível, mesmo que seja diferente do array de substituição. Se essa matriz não tiver elementos, ela se comportará como se tivesse um elemento, mas o comportamento é indefinido se for feita qualquer tentativa de acessar esse elemento ou gerar um ponteiro após ele.

    Ou em outras palavras, se você tem:

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

    Você pode acessar var->data com índices em [0, extra) . Note que sizeof(struct something) só dará o tamanho da contabilidade para as outras variables, isto é, dá aos data um tamanho de 0.


    Pode ser interessante notar também como o padrão realmente dá exemplos de como construir um tal constructo (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])); 

    Outra nota interessante pelo padrão no mesmo local é (ênfase minha):

    supondo que a chamada para malloc seja bem-sucedida, o object apontado por p se comporta, para a maioria dos propósitos, como se p tivesse sido declarado como:

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

    (há circunstâncias em que essa equivalência é quebrada; em particular, as compensações do membro d podem não ser as mesmas ).

    Isso é um truque, na verdade, para o GCC ( C90 ), na verdade.

    Também é chamado de struct hack .

    Então, da próxima vez, eu diria:

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

    Será equivalente a dizer:

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

    E eu posso criar qualquer número desses objects struct.

    A idéia é permitir uma matriz de tamanho variável no final da estrutura. Presumivelmente, bts_action é algum pacote de dados com um header de tamanho fixo (os campos de type e size ) e membro de data tamanho variável. Ao declará-lo como um array de tamanho 0, ele pode ser indexado como qualquer outro array. Em seguida, você bts_action uma estrutura bts_action , digamos de tamanho de data 1024 bytes, assim:

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

    Veja também: http://c2.com/cgi/wiki?StructHack

    O código não é válido C ( veja isto ). O kernel do Linux é, por razões óbvias, não o menor preocupação com a portabilidade, então ele usa muito código não-padrão.

    O que eles estão fazendo é uma extensão não padrão do GCC com tamanho de array 0. Um programa compatível com o padrão teria escrito u8 data[]; e isso significaria a mesma coisa. Os autores do kernel do Linux aparentemente adoram tornar as coisas desnecessariamente complicadas e não-padrão, se uma opção para fazê-lo se revelar.

    Em padrões C mais antigos, terminar uma struct com um array vazio era conhecido como “o struct hack”. Outros já explicaram seu propósito em outras respostas. O struct hack, no padrão C90, era um comportamento indefinido e poderia causar falhas, principalmente porque um compilador C está livre para adicionar qualquer número de bytes de preenchimento no final da estrutura. Esses bytes de preenchimento podem colidir com os dados que você tentou “hackear” no final da estrutura.

    O GCC desde o início fez uma extensão não padronizada para mudar isso de um comportamento indefinido para um comportamento bem definido. O padrão C99 então adaptou este conceito e qualquer programa C moderno pode, portanto, usar esse recurso sem risco. É conhecido como membro de matriz flexível em C99 / C11.

    Outro uso não tão freqüente do array de comprimento zero é obter um label nomeado dentro de uma struct.

    Suponha que você tenha algumas definições de struct grandes (abrangendo várias linhas de cache) que você deseja certificar-se de que estejam alinhadas ao limite da linha de cache, tanto no início quanto no meio, onde cruza o limite.

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

    No código, você pode declará-los usando as extensões do GCC, como:

     __attribute__((aligned(CACHE_LINE_BYTES))) 

    Mas você ainda quer ter certeza de que isso é aplicado em tempo de execução.

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

    Isso funcionaria para uma única estrutura, mas seria difícil cobrir muitas estruturas, cada uma com um nome de membro diferente para ser alinhado. Você provavelmente obteria código como abaixo, onde você tem que encontrar nomes do primeiro membro de cada estrutura:

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

    Em vez de seguir este caminho, você pode declarar uma matriz de comprimento zero na estrutura atuando como um label nomeado com um nome consistente, mas não consome nenhum espaço.

     #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 .... }; 

    Então o código de asserção de tempo de execução seria muito mais fácil de manter:

     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);