【问题标题】:x86, C++, gcc and memory alignmentx86、C++、gcc 和内存对齐
【发布时间】:2014-11-26 13:48:38
【问题描述】:

我有这个简单的 C++ 代码:

int testFunction(int* input, long length) {
    int sum = 0;
    for (long i = 0; i < length; ++i) {
        sum += input[i];
    }
    return sum;
}


#include <stdlib.h>
#include <iostream>
using namespace std;
int main()
{
    union{
        int* input;
        char* cinput;
    };

    size_t length = 1024;
    input = new int[length];


    //cinput++;

    cout<<testFunction(input, length-1);

}

如果我使用带有 -O3 的 g++ 4.9.2 编译它,它运行良好。我预计如果我取消注释倒数第二行,它会运行得更慢,但是它会在 SIGSEGV 中彻底崩溃。

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400754 in main ()
(gdb) disassemble 
Dump of assembler code for function main:
   0x00000000004006e0 <+0>:     sub    $0x8,%rsp
   0x00000000004006e4 <+4>:     movabs $0x100000000,%rdi
   0x00000000004006ee <+14>:    callq  0x400690 <_Znam@plt>
   0x00000000004006f3 <+19>:    lea    0x1(%rax),%rdx
   0x00000000004006f7 <+23>:    and    $0xf,%edx
   0x00000000004006fa <+26>:    shr    $0x2,%rdx
   0x00000000004006fe <+30>:    neg    %rdx
   0x0000000000400701 <+33>:    and    $0x3,%edx
   0x0000000000400704 <+36>:    je     0x4007cc <main+236>
   0x000000000040070a <+42>:    cmp    $0x1,%rdx
   0x000000000040070e <+46>:    mov    0x1(%rax),%esi
   0x0000000000400711 <+49>:    je     0x4007f1 <main+273>
   0x0000000000400717 <+55>:    add    0x5(%rax),%esi
   0x000000000040071a <+58>:    cmp    $0x3,%rdx
   0x000000000040071e <+62>:    jne    0x4007e1 <main+257>
   0x0000000000400724 <+68>:    add    0x9(%rax),%esi
   0x0000000000400727 <+71>:    mov    $0x3ffffffc,%r9d
   0x000000000040072d <+77>:    mov    $0x3,%edi
   0x0000000000400732 <+82>:    mov    $0x3fffffff,%r8d
   0x0000000000400738 <+88>:    sub    %rdx,%r8
   0x000000000040073b <+91>:    pxor   %xmm0,%xmm0
   0x000000000040073f <+95>:    lea    0x1(%rax,%rdx,4),%rcx
   0x0000000000400744 <+100>:   xor    %edx,%edx
   0x0000000000400746 <+102>:   nopw   %cs:0x0(%rax,%rax,1)
   0x0000000000400750 <+112>:   add    $0x1,%rdx
=> 0x0000000000400754 <+116>:   paddd  (%rcx),%xmm0
   0x0000000000400758 <+120>:   add    $0x10,%rcx
   0x000000000040075c <+124>:   cmp    $0xffffffe,%rdx
   0x0000000000400763 <+131>:   jbe    0x400750 <main+112>
   0x0000000000400765 <+133>:   movdqa %xmm0,%xmm1
   0x0000000000400769 <+137>:   lea    -0x3ffffffc(%r9),%rcx
---Type <return> to continue, or q <return> to quit---

为什么会崩溃?它是编译器错误吗?我是否导致了一些未定义的行为?编译器是否期望整数始终是 4 字节对齐的?

我也在clang上测试过,没有崩溃。

这是 g++ 的汇编输出:http://pastebin.com/CJdCDCs4

【问题讨论】:

  • "是编译器错误吗?" -- 如果你不得不问这个问题,答案通常是显而易见的。

标签: c++ gcc x86-64 memory-alignment


【解决方案1】:

代码input = new int[length]; cinput++; 导致未定义的行为,因为第二条语句正在读取一个未激活的联合成员。

即使忽略这一点,testFunction(input, length-1) 也会出于同样的原因再次出现未定义的行为。

即使忽略这一点,sum 循环也会通过错误类型的左值访问对象,该左值具有未定义的行为。

即使忽略这一点,从未初始化的对象中读取,就像您的 sum 循环所做的那样,也会再次出现未定义的行为。

【讨论】:

  • 注意,gcc 明确支持通过联合进行类型双关语,因此答案并不那么简单。
  • 但是类型双关仅在您通过联合类型访问变量时才有效,在main 编译器知道联合成员相互别名,但在testFunction 内部,编译器假定没有类型双关继续。
  • @ShafikYaghmour:你可以随意输入双关语;求和代码仍在通过错误类型的泛左值访问对象,尽管有联合,但它本身就是 UB。
  • @ShafikYaghmour “未激活的联合成员”和“错误类型的左值”是什么意思?
【解决方案2】:

gcc 已使用 SSE 指令对循环进行矢量化。 paddd(与大多数 SSE 指令一样)需要 16 字节对齐。我没有详细查看paddd 之前的代码,但我希望它最初假设 4 字节对齐,使用标量代码迭代(其中不对齐只会导致性能损失,而不是崩溃),直到它可以假设 16 字节对齐,然后进入 SIMD 循环,一次处理 4 个整数。通过添加 1 个字节的偏移量,您打破了整数数组 4 字节对齐的前提条件,之后所有的赌注都被取消了。如果您要对未对齐的数据做一些讨厌的事情(我强烈建议您不要这样做),那么您应该禁用自动矢量化 (gcc -fno-tree-vectorize)。

【讨论】:

  • 4 字节假设从何而来?它在 C++ 规范中吗?
  • 您必须与语言律师交谈,但通常对于 C/C++ 等,所有类型都被假定为“自然对齐”,除非您做了一些讨厌的事情来颠覆这一点,例如使用#pragma pack 在结构上。在许多平台(除了 x86)上,访问 any 未对齐的数据元素会导致异常,但是 x86 程序员已经习惯了能够做这样的事情。但这不是一般情况,充其量仍会导致性能下降。
【解决方案3】:

崩溃的指令是paddd(您将其突出显示)。该名称是“packed add doubleword”的缩写(参见例如here)——它是 SSE 指令集的一部分。这些指令需要对齐的指针;例如,上面的链接描述了paddd 可能导致的异常:

GP(0)

...(仅限 128 位操作)

如果内存操作数未在 16 字节边界上对齐,则与段无关。

这正是你的情况。编译器以这样一种方式排列代码,使其可以使用这些快速的 128 位操作,如 paddd,而你用你的 union 技巧颠覆了它。

我猜clang生成的代码不使用SSE,所以它对对齐不敏感。如果是这样,它也可能要慢得多(但你不会注意到它只有 1024 次迭代)。

【讨论】:

  • 关于你的最后一段,总结一个 large 数组的元素(即一个不适合缓存的)是一个内存绑定操作,它不会发生矢量化时更快。
猜你喜欢
  • 2017-01-04
  • 1970-01-01
  • 2011-02-02
  • 2011-02-10
  • 1970-01-01
  • 1970-01-01
  • 2021-12-02
  • 2017-11-15
  • 2015-09-24
相关资源
最近更新 更多