繁体   English   中英

x86,C ++,gcc和内存对齐

[英]x86, C++, gcc and memory alignment

我有这个简单的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);

}

如果我用g ++ 4.9.2和-O3编译它,它运行正常。 我预计,如果我取消注释倒数第二行,它将运行得更慢,但它会直接与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字节对齐的?

我也在铿锵声中测试了它并且没有崩溃。

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

代码input = new int[length]; cinput++; input = new int[length]; cinput++; 导致未定义的行为,因为第二个语句是从非活动的联合成员读取。

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

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

即使忽略这一点,从未初始化的对象中读取,就像你的求和循环一样,也会有不确定的行为。

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

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

GP(0)

...(仅限128位操作)

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

这正是你的情况。 编译器安排代码的方式是它可以使用这些快速的128位操作,如paddd ,并用你的union技巧破坏它。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM