2014-11-26 3 views
4

내가 ++ 코드이 간단한 C 있습니다86, C++, GCC 및 메모리 정렬

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와 4.9.2 ++ g으로 컴파일하는 경우, 그것을 잘 실행됩니다. 끝에서 두 번째 줄의 주석을 없애면 느리게 실행되지만 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 <[email protected]> 
    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--- 

왜 충돌이 발생합니까? 컴파일러 버그입니까? 정의되지 않은 동작이 발생합니까? 컴파일러는 int가 항상 4 바이트 단위로 정렬 될 것으로 기대합니까?

나는 또한 clang에서 테스트했으며 충돌이 없습니다. http://pastebin.com/CJdCDCs4

+4

"컴파일러 버그입니까?" - 당신이 그것을 묻는다면, 그 대답은 대개 명백합니다. –

답변

4

코드 input = new int[length]; cinput++; 원인 정의되지 않은 동작 두 번째 문장이 활성화되지 조합원에서 읽기 때문에 : 여기

는 g ++의 조립 출력입니다.

심지어 이것을 무시하고도 testFunction(input, length-1)은 같은 이유로 다시 정의되지 않은 동작을합니다.

이것을 무시하더라도, sum 루프는 정의되지 않은 동작이있는 잘못된 유형의 glvalue를 통해 객체에 액세스합니다.

초기화 루프의 경우처럼 초기화되지 않은 개체에서 읽는 경우에도 정의되지 않은 동작이 다시 발생합니다.

+2

참고로,'gcc'는 명시 적으로 노조를 통한 타입 페닝을 지원하므로 응답이 간단하지 않습니다. –

+0

[공용체를 통해 유형을 panning 할 때 gcc 참조] (https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Type-punning) –

+1

하지만 조합을 통해 변수에 액세스하는 경우에만 유형 핑이 작동합니다 컴파일러는 'main'에서 컴파일러가 서로의 별칭을 알지 만,'testFunction' 안에 컴파일러는 아무런 타입도 없다고 가정합니다. –

4

gcc는 SSE 명령어로 루프를 벡터화했습니다. paddd (대부분의 SSE 명령어와 유사)에는 16 바이트 정렬이 필요합니다. 나는 세부적으로 paddd 이전의 코드를 보지 않았지만 처음에는 4 바이트 정렬을 가정하고 16 바이트 정렬을 가정 할 수있을 때까지 스칼라 코드로 반복합니다 (misalignment는 성능 페널티가 발생하고 크래시는 발생하지 않음) SIMD 루프에 들어가서 한 번에 4 개의 int를 처리합니다. 1 바이트의 오프셋을 추가하면 int 배열에 대한 4 바이트 정렬의 전제 조건을 위반하게되며 그 후에 모든 베팅은 해제됩니다. 잘못 정렬 된 데이터로 불쾌한 일을하려고한다면 자동 벡터화 (gcc -fno-tree-vectorize)를 사용하지 않도록 설정해야합니다.

+0

4 바이트 가정은 어디서 오는가? C++ 사양입니까? – user697683

+0

당신은 언어 변호사와 이야기해야 할 것입니다. 그러나 C/C++ 외에도 일반적으로 '자연스럽게 정렬'되어 있다고 가정합니다. 예를 들어'#pragma pack '을 구조체. * x86 이외의 많은 플랫폼에서 정렬되지 않은 데이터 요소에 액세스하면 예외가 발생하지만 x86 프로그래머는 이와 같은 작업을 수행하는 데 익숙해졌습니다. 그것은 일반적인 경우는 아니지만 기껏해야 성능 저하가 발생합니다. –

2

충돌 한 명령어는 paddd (강조 표시 한 것)입니다. 이름은 "packed add doubleword"(예 : here 참조)의 줄임말이며 SSE 명령어 세트의 일부입니다. 이러한 명령어에는 정렬 된 포인터가 필요합니다. 예를 들어, 링크는 전술 paddd가 발생할 수 있음을 예외의 설명을 가지고

GP (0)

(128 비트 연산 만)

메모리 피연산자가 없으면 세그먼트에 관계없이 16 바이트 경계에 정렬됩니다.

정확히 일치합니다. 컴파일러는 paddd과 같은 빠른 128 비트 연산을 사용할 수있는 방식으로 코드를 배열했으며 사용자는 union 트릭으로 코드를 변조했습니다.

나는 clang에 의해 생성 된 코드가 SSE를 사용하지 않으므로 alighnment에 민감하지 않다는 것을 알 수 있습니다. 그렇다면 아마 훨씬 느려질 것입니다 (그러나 단지 1024 번의 반복으로는 알 수 없을 것입니다).

+0

마지막 단락과 관련하여 _large_ 배열 (즉, 캐시에 맞지 않는 배열)의 요소를 요약하면 메모리 바인딩 연산이며 벡터화 될 때 더 빠르게 발생하지는 않습니다. –