【问题标题】:Are stack variables aligned by the GCC __attribute__((aligned(x)))?GCC __attribute__((aligned(x))) 是否对齐了堆栈变量?
【发布时间】:2010-10-24 21:28:36
【问题描述】:

我有以下代码:

#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))) 到底是做什么的?我误解了this的解释?

我使用的是 gcc 4.1.2。

【问题讨论】:

    标签: c gcc memory-alignment callstack


    【解决方案1】:

    我认为问题在于您的数组在堆栈上,并且您的编译器太旧而无法支持过度对齐的堆栈变量。 GCC 4.6 及更高版本fixed that bug

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

    (在 C11 中,#include &lt;stdalign.h&gt;#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
    

    这是预期的。使用你的原始代码,我只是像你一样得到随机值。

    【讨论】:

    • +1 正确答案。另一种解决方案是使本地数组静态。堆栈上的对齐总是一个问题,最好养成避免它的习惯。
    • 哦,是的,我没想过让它成为静态的。这是一个好主意,因为它可以防止名称冲突。我将编辑我的答案。
    • 请注意,将其设为静态也会使其不可重入和非线程安全。
    • 即使在堆栈上,gcc 4.6+ 也能正确处理这个问题。
    • 这个答案曾经是正确的,但现在不是了。 gcc 与 4.6 一样旧,可能更早,知道如何对齐堆栈指针以正确实现 C11 / C++11 alignas(64) 或任何具有自动存储的对象。当然还有 GNU C __attribute((aligned((64)))
    【解决方案2】:

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

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

    我已经用两个不同版本的 gcc 尝试了上面的代码:来自 RedHat 5.7 的 4.1.2 框,并且它与您的问题类似地失败了(本地数组在 0x1000 字节边界上没有对齐)。然后我在 gcc 4.4.6 上尝试了你的代码 RedHat 6.3,它完美地工作(本地数组是对齐的)。 Myth TV 的人也有类似的问题(上面的 gcc 补丁似乎已修复):

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

    无论如何,您似乎在 gcc 中发现了一个错误,该错误似乎已在以后的版本中修复。

    【讨论】:

    • 根据链接的错误,gcc 4.6 是第一个针对所有架构完全修复此问题的版本。
    • 除此之外,由 gcc 生成的用于在堆栈上创建对齐变量的汇编代码是如此可怕且未经优化。那么,在堆栈上分配对齐的变量而不是调用memalign() 有意义吗?
    【解决方案3】:

    最近的 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
    

    【讨论】:

    • 这有点令人惊讶,考虑到数组是在堆栈中分配的——这是否意味着堆栈现在充满了漏洞?
    • 或者他的栈是16字节对齐的。
    【解决方案4】:

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

    #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 Case 评论,最初的问题是由于 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:在 main() 中引入非静态 a[] 并使用 GCC 3.4.6 进行编译会破坏结构数组的对齐指令,但结构之间的距离保持 0x1000……仍然很糟糕! (有关解决方法,请参阅@zifre 答案)

    【讨论】:

    • 正如 zifre 回答的那样,这不是类型,而是您在版本中将其设为静态这一事实。
    • @ysap,是 GCC 版本和全局定义使它工作。谢谢你的评论!我编辑了答案以修复它。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-04
    • 1970-01-01
    • 1970-01-01
    • 2018-07-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多