Por que um int longo leva 12 bytes em algumas máquinas?

26

Percebi algo estranho depois de compilar esse código na minha máquina:

#include <stdio.h>

int main()
{
    printf("Hello, World!\n");

    int a,b,c,d;

    int e,f,g;

    long int h;

    printf("The addresses are:\n %0x \n %0x \n %0x \n %0x \n %0x \n %0x \n %0x \n %0x",
        &a,&b,&c,&d,&e,&f,&g,&h);

    return 0;
}

O resultado é o seguinte. Observe que entre todos os endereços int há uma diferença de 4 bytes. No entanto, entre o último int e o int longo, há uma diferença de 12 bytes:

 Hello, World!
 The addresses are:

 da54dcac 
 da54dca8 
 da54dca4 
 da54dca0 
 da54dc9c 
 da54dc98 
 da54dc94 
 da54dc88
yoyo_fun
fonte
3
Coloque outro intdepois hno código fonte. O compilador pode colocá-lo na lacuna, antes h.
ctrl-alt-Delor
32
Não use a diferença entre endereços de memória para determinar o tamanho. Há uma sizeoffunção para isso. printf("size: %d ", sizeof(long));
Chris Schneider
10
Você está imprimindo apenas os baixos 4 bytes de seus endereços com %x. Para sua sorte, acontece que funcione corretamente em sua plataforma para passar argumentos de ponteiro com uma sequência de formato unsigned intesperada, mas ponteiros e ints são de tamanhos diferentes em muitas ABIs. Use %ppara imprimir ponteiros em código portátil. (É fácil imaginar um sistema em que o código seria imprimir superior / metades inferiores das primeiras 4 ponteiros, em vez de metade inferior de todos os 8.)
Peter Cordes
5
@ChrisSchneider para imprimir size_t use%zu . @yoyo_fun para imprimir endereços de uso%p . Usando o especificador formato errado invoca um comportamento indefinido
phuclv
2
@luu não espalhe informações erradas. Nenhum compilador decente se importa com a ordem em que as variáveis ​​são declaradas em C. Se importa, não há razão para fazê-lo da maneira que você descreve.
precisa saber é o seguinte

Respostas:

81

Não demorou 12 bytes, apenas oito. No entanto, o alinhamento padrão para um int de 8 bytes nesta plataforma é de 8 bytes. Como tal, o compilador precisava mover o int longo para um endereço divisível por 8. O endereço "óbvio", da54dc8c, não é divisível por 8, daí a diferença de 12 bytes.

Você deve poder testar isso. Se você adicionar outro int antes do long, então existem 8 deles, deverá achar que o int longo estará alinhado ok sem movimento. Agora serão apenas 8 bytes do endereço anterior.

Provavelmente vale ressaltar que, embora esse teste deva funcionar, você não deve confiar nas variáveis ​​que estão sendo organizadas dessa maneira. É permitido ao compilador AC fazer todo tipo de coisas estranhas para tentar executar seu programa rapidamente, incluindo reordenar variáveis ​​(com algumas ressalvas).

Alex
fonte
3
diferença, não diferença.
Deduplicator
10
"incluindo reordenar variáveis". Se o compilador decide que você não usa duas variáveis ao mesmo tempo, ele é livre para se sobrepõem parcialmente ou completamente sobrepor-los também ...
Roger Lipscombe
8
Ou, de fato, mantenha-os em registros em vez de na pilha.
Stop Harming Monica
11
@ OrangeDog Acho que isso não aconteceria se o endereço fosse usado como neste caso, mas, em geral, você está correto.
Alex
5
@ Alex: Você pode obter coisas engraçadas com memória e registros ao usar o endereço. Pegar o endereço significa que ele precisa fornecer um local de memória, mas não significa que ele precisa usá-lo. Se você pegar o endereço, atribuir 3 a ele e passá-lo para outra função, ele poderá simplesmente escrever 3 no RDI e chamar, nunca gravando na memória. Surpreendente em um depurador, às vezes.
Zan Lynx
9

Isso ocorre porque seu compilador está gerando preenchimento extra entre variáveis ​​para garantir que elas estejam alinhadas corretamente na memória.

Na maioria dos processadores modernos, se um valor tiver um endereço múltiplo de seu tamanho, será mais eficiente acessá-lo. Se tivesse colocado hno primeiro local disponível, seu endereço seria 0xda54dc8c, que não é múltiplo de 8, portanto seria menos eficiente de usar. O compilador sabe disso e está adicionando um pouco de espaço não utilizado entre as duas últimas variáveis ​​para garantir que isso aconteça.

Jules
fonte
Obrigada pelo esclarecimento. Você pode me indicar alguns materiais sobre as razões pelas quais acessar variáveis ​​que são múltiplas de seus tamanhos é mais eficiente? eu gostaria de saber por que isso está acontecendo?
yoyo_fun 12/09
4
@yoyo_fun e se você realmente quer entender a memória, então há um artigo famoso sobre o assunto futuretech.blinkenlights.nl/misc/cpumemory.pdf
Alex
11
@yoyo_fun É bem simples. Alguns controladores de memória podem acessar apenas múltiplos da largura de bits do processador (por exemplo, um processador de 32 bits pode solicitar diretamente diretamente os endereços 0-3, 4-7, 8-11, etc.). Se você solicitar um endereço não alinhado, o processador precisará fazer duas solicitações de memória e colocar os dados no registro. Portanto, voltando a 32 bits, se você quiser um valor armazenado no endereço 1, o processador precisará solicitar os endereços 0-3, 4-7 e obter os bytes de 1, 2, 3 e 4. Quatro bytes de memória perdida.
phyrfox
2
Ponto secundário, mas o acesso à memória desalinhado pode ser uma falha irrecuperável, em vez de uma ocorrência de desempenho. Dependente da arquitetura.
Jon Chesterfield
11
@JonChesterfield - Sim. É por isso que comentei que a descrição que forneci se aplica à maioria das arquiteturas modernas (pela qual eu quis dizer principalmente x86 e ARM). Existem outros que se comportam de maneiras diferentes, mas são substancialmente menos comuns. (Curiosamente: ARM usado para ser uma das arquitecturas que exigia alinhados acesso, mas eles adicionado a manipulação automática de acesso desalinhado em revisões posteriores)
Jules
2

Seu teste não está necessariamente testando o que você pensa que é, porque não há requisito do idioma para relacionar o endereço de qualquer uma dessas variáveis ​​locais entre si.

Você precisaria colocá-los como campos em uma estrutura para poder deduzir algo sobre a alocação de armazenamento.

Variáveis ​​locais não são necessárias para compartilhar armazenamento próximo umas das outras de qualquer maneira específica. O compilador pode inserir uma variável temporária em qualquer lugar da pilha, por exemplo, que pode estar entre duas dessas variáveis ​​locais.

Por outro lado, não seria permitido inserir uma variável temporária em uma estrutura; portanto, se você imprimisse os endereços dos campos de estrutura, compararia os itens alocados a partir do mesmo bloco de memória lógica (a estrutura).

Erik Eidt
fonte