Dev (Back & Front)ARTIGO

Como structs realmente funcionam em C

Você não sabe disso ainda, mas você está… errado.

Eu nem vou me incomodar em fazer a pergunta. Não faz sentido, a não ser te deixar sem graça e me fazer parecer um idiota… e eu já pareço idiota o suficiente.

Então vamos deixar pra lá.

Espere, você ainda quer ouvir a pergunta?

Sério?

Ok, então. Como você é muito esperto, me responda isto: qual é o objetivo de um struct em C?

Ahn, o que é isso que você disse? O objetivo de um struct é bla, de campos bla bla bla de containers bla bla bla de tipos de dados de bla bla bla bla?

Então, adivinhe só? Você está errado.

Você quer saber o o real objetivo de um struct em C?

O objetivo de um struct é confundir completamente os programadores que se esqueceram, ou nunca aprenderam, que no C, a memória é rei. 

Como a memória é rei? Bem, vamos ver…

1) Em C, um struct mapeia para a memória

Digamos que você quer fazer algo com um arquivo de vídeo. Na memória, o arquivo se parece com isto:

Ele usa dois bytes para o header, um byte para o tipo de vídeo, quatro bytes para o tamanho e 1024 bytes para os dados de vídeo.

Bem fácil ver como isso irá mapear para um struct, certo?

struct video_file{
short header;
char type;
int size;
char data[1024];
};

Você usa memcpy() ou um apontador direto para acessá-lo:

struct video_file v;
memcpy(&v, video_loc_in_memory, sizeof(struct video_file));
size = v.size; /* or whatever */

...

struct video_file * p = video_loc_in_memory;
size = p->size; /* or whatever */

Nada mal, certo?

Bem, aqui está o problema: se padding bytes estão envolvidos, você poderia estar entrando em um mundo de dor sem saber.

Muitos compiladores inserem padding bytes dentro do struct para garantir que os campos estejam alinhados com os bytes. O que é pior é que eles às vezes fazem isso por padrão. Se isso acontecer, você pode acabar com um struct que, internamente, se parece com isto:

struct video_file{
char padding; /*what?!?!*/
short header;
char type;
char padding; /*what?!?!*/
int size;
char data[1024];
};

Então em vez de 1031 bytes, você tem um struct com 1033 bytes. Surpresa! Tente fazer um memcpy() agora e veja o que acontece!

A solução é usar um compiler switch ou um #pragma pack para garantir que os structs estão empacotados (por exemplo, com nenhum deslocamento aplicado).

2) Em C, structs podem “extrapolar o seu tamanho”

Na verdade, arquivos de vídeo não têm tamanho estático (e contêm muito mais dados do que 1024 bytes!). Então, vamos reescrever nosso struct de maneira que seja garantido a dar a um novato ou novata sua dose diária de “que merda é essa”:

struct video_file2{
short header;
char type;
int size;
char data[0];
};

Um array de comprimento zero? Bem, lembre-se: memória é rei. Desde que um símbolo seja sustentado por memória real, você pode fazer o que quiser com ele – incluindo executá-lo além de seu limites (você nunca verá uma exceção OutOfBounds exception em C!)

Exemplo:

Esse vídeo usa dois bytes para o header, um byte para o tipo de vídeo, quatro bytes para o tamanho e n bytes para os dados de vídeo.

Esta técnica requer um apontador de struct.

struct video_file2 * p = video_loc_in_memory;
for(int i=0; i<p->size; i++)
p->data[i] = (char)(p->data[i] * 0.5); /* apply filter, maybe */

Nota: alguns compiladores não permitem arrays com comprimento zero. Nesse caso, você pode usar um array de 1 elemento. O mesmo princípio se aplica: apenas ultrapasse o final dele para atender suas necessidades.

3) Em C, você pode computar o offset dentro da estrutura podendo cometer um delito na estética do código

struct s{
short x;
char y;
char z[100];
short thing;
};

Você precisa descobrir o offset de thing dentro de s. Qual é a maneira convencional de agir com isso? Bem, dã, assim:

size_t offset = (size_t) &(((struct s*)0)->thing);

Sim, é bastante grosseiro, mas lembre-se: no C, memória é rei.

Finja que você tem um struct s no local de memória zero. Se você mapeá-lo, ele se parece com isto:

Como o struct está baseado em zero, o endereço do thing é seu offset! Nesse caso, 103 bytes.

Exceto que existe um problema, certo? Como que você irá desreferenciar um endereço de memória zero? Isso é como uma violação de acesso de memória esperando para acontecer!

Bem, na verdade, essa pergunta é complicada. Nada é desreferenciado!

(struct s*)0 projeta zero para um apontador struct s. Não ocorre desreferenciamento.
((struct s*)0)->thing informa ao compilador que você estará fazendo algo com o “thing”, mas nada acontece neste ponto. Não ocorre desreferenciamento.
&(((struct s*)0->thing) computa o endereço do “thing” usando o apontador aritmético. Ele não toca de fato a memória, então nenhum desreferenciamento ocorre.

Nota: oficialmente, você tem que usar a função offsetof() para fazer isso, mas se você um dia fizer alguma programação real em C (por exemplo, embutida), você verá isso no código de outras pessoas, então você pode ficar confortável com a maneira como ele funciona.

Conclusão

Bem, esse foi um pequeno resumo de como structs em C interagem com a memória e vice-versa. Espero que você tenha achado útil. 

?

Texto original disponível em http://tenaciousc.com/?p=3184

tem uma variada carreira de software, incluindo desenvolvimento de codecs de vídeo, engenharia de streaming de mídia, e consultoria de games. Atualmente, desenvolve aplicativos para Droid e iPhone.

Ver perfil