¿Por qué un int largo toma 12 bytes en algunas máquinas?

26

Noté algo extraño después de compilar este código en mi 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;
}

El resultado es el siguiente. Observe que entre cada dirección int hay una diferencia de 4 bytes. Sin embargo, entre el último int y el int largo hay una diferencia de 12 bytes:

 Hello, World!
 The addresses are:

 da54dcac 
 da54dca8 
 da54dca4 
 da54dca0 
 da54dc9c 
 da54dc98 
 da54dc94 
 da54dc88
yoyo_fun
fuente
3
Pon otro intdespués hen el código fuente. El compilador puede ponerlo en el vacío, antes h.
ctrl-alt-delor
32
No use la diferencia entre las direcciones de memoria para determinar el tamaño. Hay una sizeoffunción para eso. printf("size: %d ", sizeof(long));
Chris Schneider
10
Solo está imprimiendo los 4 bytes bajos de sus direcciones con %x. Afortunadamente para ti, funciona correctamente en tu plataforma para pasar argumentos de puntero con una cadena de formato esperada unsigned int, pero los punteros y las entradas son de diferentes tamaños en muchas ABI. Úselo %ppara imprimir punteros en código portátil. (Es fácil imaginar un sistema en el que su código imprima las mitades superior / inferior de los primeros 4 punteros, en lugar de la mitad inferior de los 8).
Peter Cordes
55
@ChrisSchneider para imprimir size_t uso%zu . @yoyo_fun para imprimir el uso de direcciones%p . El uso del especificador de formato incorrecto invoca un comportamiento indefinido
phuclv
2
@luu no difunda información errónea. Ningún compilador decente se preocupa por el orden en que las variables se declaran en C. Si le importa, no hay razón para que lo haga de la manera que usted describe.
gnasher729

Respuestas:

81

No tomó 12 bytes, solo tomó 8. Sin embargo, la alineación predeterminada para una longitud int de 8 bytes en esta plataforma es de 8 bytes. Como tal, el compilador necesitaba mover el int largo a una dirección que es divisible por 8. La dirección "obvia", da54dc8c, no es divisible por 8, de ahí el espacio de 12 bytes.

Deberías poder probar esto. Si agrega otro int antes del largo, entonces hay 8 de ellos, debería encontrar que el int largo se alineará bien sin un movimiento. Ahora serán solo 8 bytes de la dirección anterior.

Probablemente valga la pena señalar que, aunque esta prueba debería funcionar, no debe confiar en las variables que se organizan de esta manera. El compilador de AC puede hacer todo tipo de cosas funky para intentar que su programa se ejecute rápidamente, incluidas las variables de reordenamiento (con algunas advertencias).

Alex
fuente
3
diferencia, no brecha.
Deduplicador
10
"incluidas las variables de reordenamiento". Si el compilador decide que no usa dos variables al mismo tiempo, es libre de superponerlas parcialmente o superponerlas también ...
Roger Lipscombe
8
O, de hecho, manténgalos en registros en lugar de en la pila.
Deja de dañar a Monica el
11
@OrangeDog No creo que eso suceda si se toma la dirección como en este caso, pero, en general, por supuesto, está en lo correcto.
Alex
55
@ Alex: Puedes obtener cosas divertidas con la memoria y los registros al tomar la dirección. Tomar la dirección significa que debe darle una ubicación de memoria, pero no significa que realmente deba usarla. Si toma la dirección, asígnele 3 y páselo a otra función, podría escribir 3 en RDI y llamar, nunca escribirlo en la memoria. Sorprendente en un depurador a veces.
Zan Lynx
9

Esto se debe a que su compilador está generando relleno adicional entre las variables para garantizar que estén alineadas correctamente en la memoria.

En la mayoría de los procesadores modernos, si un valor tiene una dirección que es un múltiplo de su tamaño, es más eficiente acceder a él. Si se hubiera colocado hen el primer lugar disponible, su dirección habría sido 0xda54dc8c, que no es un múltiplo de 8, por lo que habría sido menos eficiente de usar. El compilador sabe sobre esto y está agregando un poco de espacio no utilizado entre sus dos últimas variables para asegurarse de que suceda.

Jules
fuente
Gracias por la explicación. ¿Me puede indicar algunos materiales sobre las razones por las cuales es más eficiente acceder a variables que son múltiples de sus tamaños? Me gustaría saber por qué sucede esto.
yoyo_fun
44
@yoyo_fun y si realmente quieres entender la memoria, entonces hay un artículo famoso sobre el tema futuretech.blinkenlights.nl/misc/cpumemory.pdf
Alex
1
@yoyo_fun Es bastante simple. Algunos controladores de memoria solo pueden acceder a múltiplos del ancho de bits del procesador (por ejemplo, un procesador de 32 bits solo puede solicitar directamente las direcciones 0-3, 4-7, 8-11, etc.). Si solicita una dirección no alineada, el procesador debe realizar dos solicitudes de memoria y luego ingresar los datos en el registro. Entonces, volviendo a 32 bits, si desea un valor almacenado en la dirección 1, el procesador debe solicitar las direcciones 0-3, 4-7, luego obtener los bytes de 1, 2, 3 y 4. Cuatro bytes de lectura de memoria desperdiciada.
phyrfox
2
Punto menor, pero el acceso a la memoria desalineado puede ser una falla irrecuperable en lugar de un impacto en el rendimiento. Depende de la arquitectura.
Jon Chesterfield
1
@ JonChesterfield - Sí. Es por eso que comenté que la descripción que proporcioné se aplica a la mayoría de las arquitecturas modernas (con lo que me refería principalmente a x86 y ARM). Hay otros que se comportan de diferentes maneras, pero son sustancialmente menos comunes. (Curiosamente: ARM solía ser una de las arquitecturas que requerían acceso alineado, pero agregaron el manejo automático del acceso no alineado en revisiones posteriores)
Julio
2

Su prueba no necesariamente prueba lo que cree que es, porque no hay ningún requisito del idioma para relacionar la dirección de cualquiera de estas variables locales entre sí.

Tendría que ponerlos como campos en una estructura para poder inferir algo sobre la asignación de almacenamiento.

No se requiere que las variables locales compartan el almacenamiento uno al lado del otro de ninguna manera en particular. El compilador puede insertar una variable temporal en cualquier lugar dentro de la pila, por ejemplo, que podría estar entre dos de estas variables locales.

Por el contrario, no se permitiría insertar una variable temporal en una estructura, por lo que si imprimiera las direcciones de los campos de estructura en su lugar, estaría comparando los elementos destinados asignados desde el mismo plato lógico de memoria (la estructura).

Erik Eidt
fuente