O alinhamento (alignment) e preechimento (padding) de estruturas de dados são conceitos importantes em certas arquiteturas de computadores. No entanto, esse assunto passa desapercebido em inúmeras situações. Felizmente, muitos compiladores (ou até os sistemas) são inteligentes o suficiente para organizarem as estruturas de dados de forma correta. Mas quando não é esse o caso, as consequências da falta de alinhamento no código podem variar de simples questões de desempenho até falhas graves.
A idéia é relativamente simples. Os computadores possuem uma unidade de medida natural, conhecida como palavra (word), que representa o número de bits que são processados a cada iteração de uma instrução – atualmente, os tamanhos mais comuns são os de 16 (embarcados), 32, e 64 bits. Quando o computador precisa acessar a memória, o acesso deve ser feito apenas em endereços que correspondam a múltiplos do tamanho de sua palavra. Uma arquitetura de 32 bits, por exemplo, consegue acessar diretamente os endereços de memória de 0x00000000 até 0x00000003, de 0x00000004 até 0x00000007, de 0x00000008 até 0x0000000B, de 0x0000000C até 0x0000000F, e assim por diante. Normalmente, essa estratégia é adotada com o intuito de tornar as instruções de máquina mais eficientes.
O problema da falta de alinhamento acontece justamente quando uma estrutura de dados é colocada em um endereço de memória que não é um múltiplo do tamanho da palavra. Consequentemente, a leitura dos dados não é realizada de forma simples. Para ler um dado colocado nos endereços de memória de 0x0000000A até 0x0000000D, por exemplo, seria necessário ler, inicialmente, as os endereços de 0x00000008 até 0x0000000B e, em seguida, os endereços de 0x0000000C até 0x0000000F. Depois, o computador precisaria realizar operações de bits para que o conteúdo de 0x0000000A até 0x0000000D pudesse ser extraído.
Então quer dizer que precisamos calcular o tamanho de todas as estruturas de dados que programamos? Não exatamente… Talvez em determinados ambientes isso até seja necessário, mas creio que na maioria deles podemos deixar a responsabilidade para o compilador. Exatamente neste ponto que entra o preenchimento! Normalmente, o comportamento padrão dos compiladores é de adicionar automaticamente bytes extras nas estruturas de dados para que elas sejam compatíveis com os limites da palavra. Isso não quer dizer que podemos ser desleixados quando programamos. Considere o código abaixo e veja o tamanho da classe C++ employee
. Nota: O código foi compilado com Visual Studio no Windows XP. Os tamanhos de cada tipo separadamente são: char
-> 1 byte; unsigned int
-> 4 bytes; short
-> 2 bytes.
#include
struct employee
{
char gender_; //1 byte + 3 bytes de preenchimento.
unsigned int age_;
short id_; //2 bytes + 2 bytes de preenchimento.
unsigned int office_;
char status_; //1 byte + 3 bytes de preenchimento.
};
int main()
{
std::cout << "Tamanho da classe emplyoee: "
<< sizeof(employee) << " bytes.\n\n";
return 0;
}
[/sourcecode]
Agora, considere o mesmo código, só que com a classe emplyoee bem organizada. Note que não foi necessário a inserção de preenchimento pelo compilador e, consequentemente, o tamanho da classe ficou bem menor. Em determinados contextos onde existem restrições críticas e o tamanho de determinado objeto tem influência significativa sobre o desempenho, esse fator pode fazer uma grande diferença. Observe que a primeira versão da classe emplyoee é quase duas vezes maior do que a segunda versão. Imagine uma aplicação em memória com centenas ou milhares ou milhões gastando quase 70% (setenta porcento) a mais de espaço do que poderia, de fato, gastar.
#include
struct employee
{
char gender_;
char status_;
short id_;
unsigned int age_;
unsigned int office_;
};
int main()
{
std::cout << "Tamanho da classe emplyoee: "
<< sizeof(employee) << " bytes.\n\n";
return 0;
}
[/sourcecode]
Quando é necessário lidar com questões de alinhamento, a melhor coisa a se fazer é verificar a documentação do compilador. Nem sempre o preenchimento padrão de bytes é o desejado. Dependendo da situação, há possibilidades de utilizar diretivas de compilação que determinem um alinhamento específico ou até mesmo que determinado dado não será acessado de maneira alinhada. Neste último caso, o compilador terá que fazer algumas manobras para permitir o acesso fora dos limites da palavra.
Leandro T. C. Melo
Ótima abordagem!!!
Principalmente quando estamos lidando com sistemas embarcados, que tudo que você puder salvar de recurso será bem vindo e válido!
E uma pena que poucas pessoas tenham consciência sobre esta importância, principalmente quando estamos lhe dando com desenvolvedores JAVA que pouco estão se lixando para coisas do tipo e esquecem que na verdade um bom desenvolvedor deve ter noções minimas de arquitetura de computadores para poder construir sistemas robustos e coerentes!
Abraços!
[]s
Obrigado!
Bom artigo…
Valeu Daniel!