¿Las variables de la pila están alineadas por el __attribute __ ((alineado (x))) de GCC?

88

tengo el siguiente código:

#include <stdio.h>

int
main(void)
{
        float a[4] __attribute__((aligned(0x1000))) = {1.0, 2.0, 3.0, 4.0};
        printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
}

Y tengo la siguiente salida:

0x7fffbfcd2da0 0x7fffbfcd2da4 0x7fffbfcd2da8 0x7fffbfcd2dac

¿Por qué la dirección de a[0]no es un múltiplo de 0x1000?

¿Qué __attribute__((aligned(x)))hace exactamente ? ¿Entendí mal esta explicación?

Estoy usando gcc 4.1.2.

cojocar
fuente

Respuestas:

98

Creo que el problema es que su matriz está en la pila y que su compilador es demasiado antiguo para admitir variables de pila sobrealineadas. GCC 4.6 y posteriores corrigieron ese error .

C11 / C ++ 11 alignas(64) float a[4];simplemente funciona para cualquier potencia de 2 alineación.
También lo hace GNU C __attribute__((aligned(x)))tal como lo estaba usando.

(En C11, #include <stdalign.h>para #define alignas _Alignas: cppref ).


Pero en su caso de una alineación muy grande, a un límite de página de 4k, es posible que no lo desee en la pila.

Debido a que el puntero de pila puede ser cualquier cosa cuando se inicia la función, no hay forma de alinear la matriz sin asignar mucho más de lo necesario y ajustarlo. (Los compiladores usarán and rsp, -4096o su equivalente y no usarán ninguno de los 0 a 4088 bytes que se asignaron; la ramificación sobre si ese espacio es lo suficientemente grande o no sería posible, pero no se hace porque las alineaciones enormes son mucho más grandes que el tamaño de la matriz u otros valores locales no son el caso normal.)

Si mueve la matriz de la función a una variable global, debería funcionar. La otra cosa que podría hacer es mantenerla como una variable local (lo cual es muy bueno), pero hágalo static. Esto evitará que se almacene en la pila. Tenga en cuenta que ambas formas no son seguras para subprocesos o recursividad, ya que solo habrá una copia de la matriz.

Con este código:

#include <stdio.h>

float a[4] __attribute__((aligned(0x1000))) = {1.0, 2.0, 3.0, 4.0};

int
main(void)
{
        printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
}

Entiendo esto:

0x804c000 0x804c004 0x804c008 0x804c00c

que es lo que se espera. Con tu código original, obtengo valores aleatorios como tú.

Zifre
fuente
11
+1 respuesta correcta. Una solución alternativa es hacer que la matriz local sea estática. La alineación en la pila siempre es un problema y es mejor tener el hábito de evitarla.
Dan Olson
Oh, sí, no pensé en hacerlo estático. Es una buena idea, ya que evita las colisiones de nombres. Editaré mi respuesta.
Zifre
3
Tenga en cuenta que hacerlo estático también lo hace no reentrante y no seguro para subprocesos.
ArchaeaSoftware
3
También gcc 4.6+ maneja esto correctamente incluso en la pila.
Textshell
1
Esta respuesta solía ser correcta, pero ahora no lo es. gcc tan antiguo como 4.6, quizás más antiguo, sabe cómo alinear el puntero de la pila para implementar correctamente C11 / C ++ 11 alignas(64)o lo que sea en objetos con almacenamiento automático. Y, por supuesto, GNU C__attribute((aligned((64)))
Peter Cordes
41

Hubo un error en gcc que causó que el atributo alineado no funcionara con las variables de pila. Parece estar arreglado con el parche vinculado a continuación. El enlace a continuación también contiene bastante discusión sobre el problema.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=16660

Probé su código anterior con dos versiones diferentes de gcc: 4.1.2 desde una caja RedHat 5.7, y falló de manera similar a su problema (las matrices locales no estaban alineadas de ninguna manera en los límites de bytes 0x1000). Luego probé su código con gcc 4.4.6 en RedHat 6.3, y funcionó perfectamente (las matrices locales estaban alineadas). La gente de Myth TV tenía un problema similar (que el parche gcc anterior parecía solucionar):

http://code.mythtv.org/trac/ticket/6535

De todos modos, parece que encontró un error en gcc, que parece estar corregido en versiones posteriores.

rts1
fuente
3
Según el error vinculado, gcc 4.6 fue la primera versión con este problema completamente solucionado para todas las arquitecturas.
TextShell
Además de eso, el código ensamblador generado por gcc para crear una variable alineada en la pila es tan horrible y tan poco optimizado. Entonces, ¿tiene sentido asignar variables alineadas en la pila en lugar de llamar memalign()?
Jérôme Pouiller
13

GCC reciente (probado con 4.5.2-8ubuntu4) parece funcionar como se esperaba con la matriz alineada correctamente.

#include <stdio.h>

int main(void)
{
    float a[4] = { 1.0, 2.0, 3.0, 4.0 };
    float b[4] __attribute__((aligned(0x1000))) = { 1.0, 2.0, 3.0, 4.0 };
    float c[4] __attribute__((aligned(0x10000))) = { 1.0, 2.0, 3.0, 4.0 };

    printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
    printf("%p %p %p %p\n", &b[0], &b[1], &b[2], &b[3]);
    printf("%p %p %p %p\n", &c[0], &c[1], &c[2], &c[3]);
}

Yo obtengo:

0x7ffffffefff0 0x7ffffffefff4 0x7ffffffefff8 0x7ffffffefffc
0x7ffffffef000 0x7ffffffef004 0x7ffffffef008 0x7ffffffef00c
0x7ffffffe0000 0x7ffffffe0004 0x7ffffffe0008 0x7ffffffe000c
Caso Caleb
fuente
Esto es un poco sorprendente, considerando que las matrices están asignadas en la pila, ¿significa que la pila ahora está llena de agujeros?
ysap
O su pila está alineada con 16 bytes.
user7116
9

La alineación no es eficaz para todos los tipos. Debería considerar el uso de una estructura para ver los atributos en acción:

#include <stdio.h>

struct my_float {
        float number;
}  __attribute__((aligned(0x1000)));

struct my_float a[4] = { {1.0}, {2.0}, {3.0}, {4.0} };

int
main(void)
{
        printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
}

Y luego leerás:

0x603000 0x604000 0x605000 0x606000

Que es lo que esperabas.

Editar: empujado por @yzap y siguiendo el comentario de @Caleb Case, el problema inicial se debe solo a la versión de GCC . Revisé GCC 3.4.6 vs GCC 4.4.1 con el código fuente del solicitante:

$ ./test_orig-3.4.6
0x7fffe217d200 0x7fffe217d204 0x7fffe217d208 0x7fffe217d20c
$ ./test_orig-4.4.1
0x7fff81db9000 0x7fff81db9004 0x7fff81db9008 0x7fff81db900c

Ahora es obvio que las versiones anteriores de GCC (en algún lugar antes de 4.4.1) muestran patologías de alineación.

Nota 1: Mi código propuesto no responde a la pregunta que entendí como "alinear cada campo de la matriz".

Nota 2: Traer un [] no estático dentro de main () y compilar con GCC 3.4.6 rompe la directiva de alineación de la matriz de estructuras pero mantiene una distancia de 0x1000 entre estructuras ... ¡aún está mal! (ver @zifre respuesta para soluciones)

levif
fuente
2
Como respondió zifre, no es el tipo, sino el hecho de que lo hizo estático en su versión.
ysap
@ysap, fue tanto la versión GCC como la definición global lo que hizo que funcionara. ¡Gracias por tu comentario! Edité la respuesta para solucionarlo. :)
levif