Matriz de comprimento zero

Eu estou trabalhando na refatoração de algum código antigo e encontrei algumas estruturas contendo matrizes de comprimento zero (abaixo). Avisos deprimidos pelo pragma, é claro, mas não consegui criar por “novas” estruturas contendo tais estruturas (erro 2233). Array ‘byData’ usado como ponteiro, mas por que não usar o ponteiro? ou matriz de comprimento 1? E, claro, nenhum comentário foi adicionado para me fazer apreciar o processo … Alguma causa para usar tal coisa? Algum conselho em refatorar aqueles?

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

NB é C ++, Windows XP, VS 2003

Sim, este é um C-Hack.
Para criar uma matriz de qualquer tamanho:

 struct someData* mallocSomeData(int size) { struct someData* result = (struct someData*)malloc(sizeof(struct someData) + size * sizeof(BYTE)); if (result) { result->nData = size; } return result; } 

Agora você tem um object de someData com uma matriz de um comprimento especificado.

Existem, infelizmente, várias razões pelas quais você declararia uma matriz de comprimento zero no final de uma estrutura. Ele basicamente permite que você tenha uma estrutura de comprimento variável retornada de uma API.

Raymond Chen fez um excelente post no blog sobre o assunto. Eu sugiro que você dê uma olhada neste post porque provavelmente contém a resposta que você deseja.

Note em seu post, ele lida com arrays de tamanho 1 em vez de 0. Esse é o caso porque arrays de comprimento zero são uma input mais recente nos padrões. Seu post ainda deve se aplicar ao seu problema.

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

EDITAR

Nota: Embora o post de Raymond diga que 0 matrizes de comprimento são legais em C99, elas ainda não são legais em C99. Em vez de uma matriz de tamanho 0 aqui você deve estar usando uma matriz de comprimento 1

Este é um hack C antigo para permitir matrizes de tamanho flexível.

No padrão C99, isso não é necessário, pois suporta a syntax arr [].

Sua intuição sobre “por que não usar uma matriz de tamanho 1” está correta.

O código está fazendo o “C struct struct” errado, porque declarações de matrizes de comprimento zero são uma violação de restrição. Isso significa que um compilador pode rejeitar seu hack logo de cara em tempo de compilation com uma mensagem de diagnóstico que interrompe a tradução.

Se quisermos perpetrar um hack, devemos passar pelo compilador.

O caminho certo para fazer o “C struct hack” (que é compatível com os dialetos C desde o ANSI C 1989, e provavelmente muito antes) é usar uma matriz perfeitamente válida de tamanho 1:

 struct someData { int nData; unsigned char byData[1]; } 

Além disso, em vez de sizeof struct someData , o tamanho da parte antes byData é calculado usando:

 offsetof(struct someData, byData); 

Para alocar uma struct someData com espaço para 42 bytes em byData , byData :

 struct someData *psd = (struct someData *) malloc(offsetof(struct someData, byData) + 42); 

Observe que esse offsetof cálculo é, de fato, o cálculo correto, mesmo no caso de o tamanho da matriz ser zero. Você vê, o sizeof toda a estrutura pode include preenchimento. Por exemplo, se tivermos algo assim:

 struct hack { unsigned long ul; char c; char foo[0]; /* assuming our compiler accepts this nonsense */ }; 

O tamanho do struct hack de struct hack é possivelmente preenchido para alinhamento devido ao membro ul . Se unsigned long tem quatro bytes de largura, então possivelmente sizeof (struct hack) é 8, enquanto offsetof(struct hack, foo) é quase certamente 5. O offsetof método é a maneira de obter o tamanho exato da parte anterior da struct pouco antes do array.

Então, essa seria a maneira de refatorar o código: torná-lo compatível com o hack de struct clássico e altamente portátil.

Por que não usar um ponteiro? Porque um ponteiro ocupa espaço extra e precisa ser inicializado.

Há outras boas razões para não usar um ponteiro, a saber, que um ponteiro requer um espaço de endereço para ser significativo. O struct hack é externalizável: isto é, há situações em que tal layout está em conformidade com o armazenamento externo, como áreas de arquivos, pacotes ou memory compartilhada, nos quais você não deseja pointers, porque eles não são significativos.

Vários anos atrás, usei o struct hack em uma interface de passagem de mensagem de memory compartilhada entre o kernel e o espaço do usuário. Eu não queria pointers, porque eles teriam significado apenas para o espaço de endereço original do processo gerando uma mensagem. A parte do kernel do software tinha uma visão da memory usando seu próprio mapeamento em um endereço diferente e, portanto, tudo era baseado em cálculos de offset.

Vale a pena apontar a IMO a melhor maneira de fazer o cálculo de tamanho, que é usado no artigo de Raymond Chen vinculado acima.

 struct foo { size_t count; int data[1]; } size_t foo_size_from_count(size_t count) { return offsetof(foo, data[count]); } 

O deslocamento da primeira input do final da alocação desejada é também o tamanho da alocação desejada. IMO é uma maneira extremamente elegante de fazer o cálculo de tamanho. Não importa qual seja o tipo de elemento da matriz de tamanho variável. O offsetof (ou FIELD_OFFSET ou UFIELD_OFFSET no Windows) é sempre escrito da mesma maneira. Não há expressões sizeof () para atrapalhar acidentalmente.