예, __attribute__((packed))
일부 시스템에서는 안전하지 않을 수 있습니다. 증상은 아마도 x86에 나타나지 않을 것인데, 이는 문제를 더욱 교활하게 만듭니다. x86 시스템에서 테스트해도 문제가 드러나지 않습니다. x86에서는 잘못 정렬 된 액세스가 하드웨어에서 처리됩니다. int*
홀수 주소를 가리키는 포인터를 역 참조하면 올바르게 정렬 된 것보다 약간 느리지 만 올바른 결과를 얻을 수 있습니다.
SPARC와 같은 일부 다른 시스템에서는 잘못 정렬 된 int
객체 에 액세스하려고 하면 버스 오류가 발생하여 프로그램이 중단됩니다.
잘못 정렬 된 액세스가 주소의 하위 비트를 조용히 무시하여 잘못된 메모리 청크에 액세스하는 시스템도 있습니다.
다음 프로그램을 고려하십시오.
#include <stdio.h>
#include <stddef.h>
int main(void)
{
struct foo {
char c;
int x;
} __attribute__((packed));
struct foo arr[2] = { { 'a', 10 }, {'b', 20 } };
int *p0 = &arr[0].x;
int *p1 = &arr[1].x;
printf("sizeof(struct foo) = %d\n", (int)sizeof(struct foo));
printf("offsetof(struct foo, c) = %d\n", (int)offsetof(struct foo, c));
printf("offsetof(struct foo, x) = %d\n", (int)offsetof(struct foo, x));
printf("arr[0].x = %d\n", arr[0].x);
printf("arr[1].x = %d\n", arr[1].x);
printf("p0 = %p\n", (void*)p0);
printf("p1 = %p\n", (void*)p1);
printf("*p0 = %d\n", *p0);
printf("*p1 = %d\n", *p1);
return 0;
}
gcc 4.5.2가 포함 된 x86 Ubuntu에서 다음과 같은 출력이 생성됩니다.
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = 0xbffc104f
p1 = 0xbffc1054
*p0 = 10
*p1 = 20
gcc 4.5.1이 설치된 SPARC Solaris 9에서 다음을 생성합니다.
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = ffbff317
p1 = ffbff31c
Bus error
두 경우 모두 프로그램은 추가 옵션없이 컴파일됩니다 gcc packed.c -o packed
.
(소위 컴파일러가 홀수 어드레스에 구조체를 할당 할 수 있기 때문에, 어레이는 신뢰성 문제가 발생하지 않는다 라기보다는 하나의 구조체를 사용하는 프로그램 x
두개의 배열 부재는 적절하게 정렬된다. struct foo
목적, 적어도 하나 이상의 다른 잘못 정렬 된 x
멤버가 있습니다.)
(이 경우 멤버 뒤 p0
의 패킹 된 int
멤버를 가리 키기 때문에 잘못 정렬 된 주소를 가리 킵니다 char
. p1
배열의 두 번째 요소에서 동일한 멤버를 가리 키므로 올바르게 정렬됩니다. 따라서 그 char
앞에 두 개의 오브젝트가 있습니다. SPARC Solaris에서는 어레이 arr
가 4의 배수가 아닌 짝수 인 주소에 할당 된 것으로 보입니다.)
이름으로 멤버 x
를 참조 할 때 struct foo
컴파일러 x
는 잠재적으로 잘못 정렬 된 것을 알고 올바르게 액세스하기 위해 추가 코드를 생성합니다.
주소가 포인터 객체에 저장 arr[0].x
되거나 arr[1].x
포인터 객체에 저장되면 컴파일러 나 실행중인 프로그램은 그것이 잘못 정렬 된 int
객체를 가리키는 지 알 수 없습니다 . 버스가 올바르게 정렬되어 버스 오류 또는 이와 유사한 다른 오류가 발생한다고 가정합니다.
gcc에서 이것을 고치는 것은 비실용적이라고 생각합니다. 일반적인 해결책은 (a) 컴파일 타임에 포인터가 패킹 된 구조체의 잘못 정렬 된 멤버를 가리 키지 않는다는 것을 입증하거나 (b) 정렬되거나 잘못 정렬 된 객체를 처리 할 수있는 더 크고 느린 코드 생성
gcc 버그 보고서를 제출했습니다 . 내가 말했듯이, 나는 그것을 고치는 것이 실용적이지 않다고 생각하지만, 문서는 그것을 언급해야한다 (현재는 그렇지 않다).
업데이트 : 2018-12-20 현재이 버그는 수정 된 것으로 표시되어 있습니다. 패치는 gcc 9에 새로운 -Waddress-of-packed-member
옵션 이 추가 되어 기본적으로 활성화됩니다.
압축 된 구조체 또는 공용체 멤버 주소를 가져 오면 정렬되지 않은 포인터 값이 발생할 수 있습니다. 이 패치는 -Waddress-of-packed-member를 추가하여 포인터 할당시 정렬을 확인하고 정렬되지 않은 주소와 정렬되지 않은 포인터를 경고합니다.
방금 소스에서 해당 버전의 gcc를 만들었습니다. 위 프로그램의 경우 다음 진단을 생성합니다.
c.c: In function ‘main’:
c.c:10:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
10 | int *p0 = &arr[0].x;
| ^~~~~~~~~
c.c:11:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
11 | int *p1 = &arr[1].x;
| ^~~~~~~~~