domingo, 30 de novembro de 2008

GCC -Wpadded e alinhamento de dados em uma struct

Estou lendo a documentação do GCC (sim... só por esporte) sobre as opções de warning e entre umas e outras que quero testar (mas tenho que antes ler sobre as outras flags). Então agora vou falar sobre a -Wpadded.


Basicamente, ela diz ao GCC para imprimir warnings quando for necessário fazer padding para ajustar o alinhamento de alguma struct.
(Para dúvidas do que seria o alinhamento na memória: http://en.wikipedia.org/wiki/Data_structure_alignment)

Por exemplo:
typedef struct _mystruct{
int tam;
char bchar[2];
double res;
char bcode[2];
} mystruct;


Quando compilo com -Wpadded, ele avisa:
$ gcc -o struct_align.o struct_align.c -Wpadded
struct_align.c:4: warning: padding struct to align ‘res’
struct_align.c:6: warning: padding struct size to alignment boundary


Ou seja... ele teve que fazer um padding (basicamente, encher com 0 ou porcarias que não serão utilizadas) a região entre bchar[2] e res, para poder deixar res alinhado.
E novamente, teve que fazer um padding no final, para deixar a struct toda (o final dela) alinhada.

Para comprovar isto, vamos ver que através do sizeof() vamos pegar o tamanho dos dados:
Sizes:
-int = 4
-char = 1
-double = 8
-mystruct = 20


Epa!
A struct era para ter 1 int, 2 vetores de 2 chars cada e 1 double.
Logo o total deveria ser 1x4 + 2x2x1 + 1x8 = 16 bytes.

Mas por que essa joça ficou ocupando 20 bytes? Exatamente por causa do padding.
Vamos criar um mapa de como a struct deveria ficar na memória, se não houvesse o padding:
(Vamos supor que nossa memória começa no 0x0000)

0x0000 - tam
0x0004 - bchar[0]
0x0005 - bchar[1]
0x0006 - res
0x000E - bcode[0]
0x000F - bcode[1]

Ou seja, num diagrama ficaria algo assim:

|----------------------------------------------------|
| END | BYTE0 | BYTE1 | BYTE2 | BYTE3 |
|----------------------------------------------------|
| 0x0000 | tam | tam | tam | tam |
|----------------------------------------------------|
| 0x0004 | bchar[0] | bchar[1] | res | res |
|----------------------------------------------------|
| 0x0008 | res | res | res | res |
|----------------------------------------------------|
| 0x000C | res | res | bcode[0] | bcode[1] |
|----------------------------------------------------|
| 0x0010 | ........................................ |
|----------------------------------------------------|



Eu tentei fazer o diagrama de modo a simplificar, com cada linha contendo 4 bytes, ou seja 32 bits... o tamanho de uma palavra de dados de um processador de 32 bits (ou um de 64 operando em modo de 32 ;).

Mas como dito antes (veja o link para a wikipedia mais acima), os dados devem estar alinhados na struct, de acordo com o maior (que ocupa mais espaço) tipo de dados utilizado na struct, então o GCC insere um padding, logo ficamos assim:

0x0000 - tam
0x0004 - bchar[0]
0x0005 - bchar[1]
0x0008 - res
0x0010 - bcode[0]
0x0011 - bcode[1]

E assim no diagrama:

|----------------------------------------------------|
| END | BYTE0 | BYTE1 | BYTE2 | BYTE3 |
|----------------------------------------------------|
| 0x0000 | tam | tam | tam | tam |
|----------------------------------------------------|
| 0x0004 | bchar[0] | bchar[1] | PAD | PAD |
|----------------------------------------------------|
| 0x0008 | res | res | res | res |
|----------------------------------------------------|
| 0x000C | res | res | res | res |
|----------------------------------------------------|
| 0x0010 | bcode[0] | bcode[1] | ............... |
|----------------------------------------------------|



Até aqui temos 18 bytes utilizados (16 que queremos e 2 de padding).
Então, o GCC preenche os 2 bytes finais também (ver OBS1 no final):

|----------------------------------------------------|
| END | BYTE0 | BYTE1 | BYTE2 | BYTE3 |
|----------------------------------------------------|
| 0x0000 | tam | tam | tam | tam |
|----------------------------------------------------|
| 0x0004 | bchar[0] | bchar[1] | PAD | PAD |
|----------------------------------------------------|
| 0x0008 | res | res | res | res |
|----------------------------------------------------|
| 0x000C | res | res | res | res |
|----------------------------------------------------|
| 0x0010 | bcode[0] | bcode[1] | PAD | PAD |
|----------------------------------------------------|
| 0x0014 | ....................................... |
|----------------------------------------------------|


Logo, temos uma estrutura que utiliza 20 bytes, quando realmente desejamos utilizar 16... isso é um overhead de 25%!


Para corrigir este tipo de problema é simples, vamos agrupar os dados que estão quebrando o alinhamento, ou seja, nesse caso vamos deixar os dois vetores de char juntos:

typedef struct _mystruct_fixed{
int tam;
double res;
char bchar[2];
char bcode[2];
} mystruct_fixed;


Poderíamos também ter movido o bcode[] para antes do res... os dois modos irão arrumar o alinhamento da struct e com isso ela passará a ocupar os 16 bytes que desejávamos. Porém eu pessoalmente acho melhor colocar todos os char[] no final, pois se desejarmos alterar o tamanho de algum deles, não teremos que nos preocupar em reorganizar tudo.



Bem... espero que tenham gostado... e pensar que escrevi isso tudo, pois só queria comentar sobre a opção -Wpadded que não é ativada pelo -Wall nem pelo -Wextra :P

Para quem quiser, estou disponibilizando um fonte (struct_align.c) aqui:
http://www.dcc.ufrj.br/~brunobuss/code/struct_align.c


Todas os testes foram feitos com a seguinte versão do GCC:
$ gcc --version
gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2






OBS1:
Naquele caso, o gcc preencheu o final da struct, pois na mesma tínhamos dados que eram alinhados em 4 bytes (ou múltiplos de 4 bytes), em um caso de uma struct do tipo:

typedef struct _mycharstruct{
char bchar[5];
char bcode[4];
} mycharstruct;


O GCC não irá incluir nenhum padding, pois não será necessário alinhar a estrutura. Logo esta struct irá ocupar 9 bytes como esperado.

Porem, caso tenhamos:

typedef struct _myshortcharstruct{
short a;
char b[1];
} myshortcharstruct;


O GCC irá fazer padding no final da struct para alinha-la com o short, ou seja... caso o tamanho de b, não seja múltiplo de 2 (short == 2 bytes), então o GCC irá colocar um byte a mais para o alinhamento.

2 comentários:

Anônimo disse...

[quilula] //O Blogger achou q era html aeihueahiueah
Ahhh, vamos economizar uns bytes aqui...
[/quilula]

Mto bom, cara!

beijo na canela esquerda o/

Anônimo disse...

Muito sagaz esse Wpadded!!! Gostei do post!!!