스택 변수가 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를 사용하고 있습니다.

답변:


98

문제는 배열이 스택에 있고 컴파일러가 너무 오래되어 과도하게 정렬 된 스택 변수를 지원할 수 없다는 것입니다. GCC 4.6 이상 에서는이 버그를 수정했습니다 .

C11 / C ++ 11 alignas(64) float a[4];Just Works for any power of 2 alignment.
사용하던 GNU C도 __attribute__((aligned(x)))마찬가지입니다.

(C11 #include <stdalign.h>에서 #define alignas _Alignas: cppref의 경우 ).


그러나 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. 다른 해결책은 로컬 배열을 정적으로 만드는 것입니다. 스택에서의 정렬은 항상 문제이며이를 피하는 습관을 갖는 것이 가장 좋습니다.
Dan Olson

네, 정적으로 만들 생각은 없었습니다. 이름 충돌을 방지하기 때문에 좋은 생각입니다. 내 대답을 편집하겠습니다.
Zifre

3
정적으로 만들면 재진입이 불가능하고 스레드로부터 안전하지 않습니다.
ArchaeaSoftware 2013 년

3
또한 gcc 4.6+는 스택에서도 이것을 올바르게 처리합니다.
textshell

1
이 대답은 예전에는 맞았지만 지금은 그렇지 않습니다. gcc는 4.6, 아마도 더 오래되었을 수도 있지만 스택 포인터를 정렬하여 C11 / C ++ 11 alignas(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으로 코드를 시도했는데 완벽하게 작동했습니다 (로컬 배열이 정렬 됨). Myth TV 사람들도 비슷한 문제가있었습니다 (위의 gcc 패치가 수정 한 것 같습니다).

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

어쨌든 gcc에서 버그를 발견 한 것 같습니다. 이는 이후 버전에서 수정 된 것으로 보입니다.


3
링크 된 버그에 따르면 gcc 4.6은 모든 아키텍처에서이 문제가 완전히 수정 된 첫 번째 릴리스였습니다.
textshell

그 외에도 스택에 정렬 된 변수를 생성하기 위해 gcc에서 생성 한 어셈블리 코드는 너무 끔찍하고 최적화되지 않았습니다. 따라서 호출하는 대신 스택에 정렬 된 변수를 할당하는 것이 합리적 memalign()입니까?
Jérôme 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

또는 그의 스택은 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 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 : 비 정적 a []를 main () 내부로 가져와 GCC 3.4.6으로 컴파일하면 구조체 배열의 정렬 지시문이 깨지지 만 구조체 사이의 거리가 0x1000으로 유지됩니다. (해결 방법은 @zifre 답변 참조)


2
zifre가 대답했듯이 유형이 아니라 버전에서 정적으로 만들었다는 사실입니다.
ysap

@ysap, GCC 버전과 글로벌 정의 모두 작동했습니다. 댓글 주셔서 감사합니다! 나는 그것을 수정하기 위해 대답을 편집했습니다. :)
levif 2012 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.