堆栈变量是否由GCC __attribute __((aligned(x)))对齐?


88

我有以下代码:

#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]);
}

我有以下输出:

0x7fffbfcd2da0 0x7fffbfcd2da4 0x7fffbfcd2da8 0x7fffbfcd2dac

为什么地址a[0]不是的倍数0x1000

到底__attribute__((aligned(x)))是什么?我误解了这个解释吗?

我正在使用gcc 4.1.2。

Answers:


98

我相信问题在于您的数组在堆栈上,并且您的编译器太旧了,无法支持过度对齐的堆栈变量。GCC 4.6及更高版本修复了该错误

C11 / C ++ 11alignas(64) float a[4];只适用于2对齐的任何幂。您使用
GNU C时__attribute__((aligned(x)))也是如此。

(在C11中,#include <stdalign.h>对于#define alignas _Alignascppref)。


但是,如果对齐范围非常大(到4k页面边界),则可能不希望将其放在堆栈上。

因为在函数启动时堆栈指针可以是任何东西,所以如果不分配过多的内存并进行调整,就无法对齐数组。(编译器将and rsp, -4096等效或不使用分配的0到4088字节中的任何一个;可能会分支该空间是否足够大,但不会进行分支,因为巨大的对齐方式比数组或其他局部大小大得多不是正常情况。)

如果将数组移出函数并移至全局变量,则该数组应该起作用。您可以做的另一件事是将其保留为局部变量(这是非常好的事情),但要使其成为static。这样可以防止将其存储在堆栈中。请注意,这两种方式都不是线程安全的或递归安全的,因为只有一个数组副本。

使用此代码:

#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]);
}

我得到这个:

0x804c000 0x804c004 0x804c008 0x804c00c

这是预期的。使用您的原始代码,我将像您一样获得随机值。


11
+1正确答案。另一种解决方案是使本地数组静态。堆栈对齐始终是一个问题,最好养成避免它的习惯。
丹·奥尔森,2009年

哦,是的,我没想到要使其静态化。这是一个好主意,因为它可以防止名称冲突。我将编辑我的答案。
2009年

3
请注意,将其设置为静态还使其不可重入且也不是线程安全的。
ArchaeaSoftware

3
gcc 4.6+甚至在堆栈上也能正确处理。
textshell

1
这个答案曾经是正确的,但现在不是。gcc早于4.6,可能更旧,它知道如何对齐堆栈指针以正确实现C11 / C ++ 11alignas(64)或具有自动存储功能的对象上的任何对象。当然还有GNU C__attribute((aligned((64)))
Peter Cordes,

41

gcc中有一个错误,导致属性对齐无法与堆栈变量一起使用。它似乎已通过下面链接的补丁修复。下面的链接也包含有关该问题的大量讨论。

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

我在上面用两种不同版本的gcc尝试了您的代码:在RedHat 5.7框中使用的是4.1.2,它与您的问题类似(本地数组根本没有对齐0x1000字节边界)失败。然后,我在RedHat 6.3上使用gcc 4.4.6尝试了您的代码,它运行得很完美(本地数组已对齐)。神话电视的人们有一个类似的问题(上面的gcc补丁似乎已解决):

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

无论如何,您似乎在gcc中发现了一个错误,该错误似乎在更高版本中已得到修复。


3
根据链接的错误,gcc 4.6是第一个发行版,此问题已在所有体系结构中完全解决。
textshell

除此之外,由gcc生成的用于在堆栈上创建对齐变量的汇编代码是如此可怕,而且没有经过优化。因此,在堆栈上分配对齐的变量而不是调用memalign()是否有意义?
杰罗姆Pouiller

13

最近的GCC(经4.5.2-8ubuntu4测试)似乎可以按预期工作,并且阵列正确对齐。

#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]);
}

我得到:

0x7ffffffefff0 0x7ffffffefff4 0x7ffffffefff8 0x7ffffffefffc
0x7ffffffef000 0x7ffffffef004 0x7ffffffef008 0x7ffffffef00c
0x7ffffffe0000 0x7ffffffe0004 0x7ffffffe0008 0x7ffffffe000c

考虑到数组是在堆栈中分配的,这有点令人惊讶-这是否意味着堆栈现在充满了孔?
ysap 2012年

或者他的堆栈是16字节对齐的。
user7116 2013年

9

对齐并非对所有类型都有效。您应该考虑使用结构来查看实际的属性:

#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]);
}

然后,您将阅读:

0x603000 0x604000 0x605000 0x606000

这是您所期望的。

编辑: 由@yzap推及以下@Caleb此案发表评论,最初的问题是由于GCC版本。我已经使用请求者的源代码检查了GCC 3.4.6与GCC 4.4.1:

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

现在很明显,较旧的GCC版本(4.4.1之前的某个版本)显示了对齐病理。

注意1:我建议的代码无法回答我理解为“对齐数组的每个字段”的问题。

注意2:将非静态a []放入main()并使用GCC 3.4.6进行编译会破坏struct数组的对齐指令,但保持结构之间的0x1000距离...仍然很糟糕!(有关解决方法,请参见@zifre答案)


2
正如zifre回答的那样,它不是类型,而是您在版本中使其变为静态的事实。
ysap 2012年

@ysap,是它的GCC版本和全局定义。感谢您的评论!我修改了答案以解决此问题。:)
levif 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.