gcc의 __attribute__(packed) / #pragma pack은 안전하지 않습니까?
C에서 컴파일러는 각 멤버가 올바르게 정렬되도록 하기 위해 선언된 순서대로 멤버 사이에 또는 마지막 멤버 뒤에 가능한 패딩 바이트를 삽입하여 구조체의 멤버를 배치한다.
확장자 gcc를 합니다.__attribute__((packed))
컴파일러에 패딩을 삽입하지 않도록 지시하여 구조 구성원을 잘못 정렬할 수 있습니다.를 들어,이 통상적으로 모든 을 필요로 경우, 모든 것을 필요로 합니다.int
바이트 얼라인먼트를 , 4 얼라인먼트를 가지는 오브젝트,__attribute__((packed))
이 될 수 있다int
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
gcc 매뉴얼 인용:
"packed" 속성은 변수 또는 구조 필드가 가능한 한 가장 작은 정렬을 가지도록 지정합니다. "aligned" 속성으로 더 큰 값을 지정하지 않는 한 변수의 경우 1바이트, 필드의 경우 1비트입니다.
이 확장자를 사용하면 데이터 요건은 작아지지만 코드는 느려질 수 있습니다.컴파일러는 (일부 플랫폼에서는) 한 번에 1바이트씩 잘못 정렬된 멤버에 액세스하기 위해 코드를 생성해야 하기 때문입니다.
하지만 이것이 안전하지 않은 경우가 있나요?컴파일러는 패킹된 구조의 잘못 정렬된 멤버에 액세스하기 위해 항상 올바른(느리지만) 코드를 생성합니까?모든 경우에 그렇게 하는 것이 가능한가?
ㅇㅇ.__attribute__((packed))
일부 시스템에서는 안전하지 않을 수 있습니다.x86에서는 않을 이 있기 에서 테스트해도.( 정렬된 됩니다.x86에서는을 참조 가 발생합니다.int*
홀수 주소를 가리키는 포인터는 올바르게 정렬된 경우보다 약간 느리지만 올바른 결과를 얻을 수 있습니다.)
등 정렬된SPARC에 .int
에 의해 버스 object가 .
또한 잘못 정렬된 액세스가 주소의 하위 비트를 무시하고 잘못된 메모리 청크에 액세스하는 시스템도 있습니다.
다음 프로그램을 고려하십시오.
#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
멤버가 올바르게 정렬되어 있습니다.의 2개의 이 있는 struct foo
정렬되어 .x
□□□□□□□□★
( 「 」 「 。p0
는, 된 주소를 있기 정렬된 주소를 있습니다.int
의 후속 char
버입니니다다p1
두 에 올바르게되어 있습니다. 2개의 멤버가 .2번char
SPARC Solaris의 오브젝트레이는 SPARC Solaris입니다arr
짝수 4의 ).
를 할 때x
struct foo
하여 "Compiler"가 "Compiler"라는 을 알고 .x
는 잘못 정렬되어 있을 가능성이 있으며 올바르게 액세스하기 위한 추가 코드를 생성합니다.
" " " " " 의가 지정되면arr[0].x
★★★★★★★★★★★★★★★★★」arr[1].x
오브젝트에 중인 도 이 정렬된 것을 있는 것을 알 수 .컴파일러도 실행 중인 프로그램도 이 오브젝트가 잘못 정렬되었음을 알 수 없습니다.int
있는 로 하고 있기 때문에 (합니다.올바르게 정렬되어 있는 것을 전제로 하고 있기 때문에, (일부 시스템에서는) 버스 에러나 같은 장해가 발생합니다.
이것을 gcc로 고치는 것은 비현실적이라고 생각합니다.일반적인 솔루션은 (a) 포인터가 잘못 정렬된 구조의 부재를 가리키지 않는다는 것을 컴파일 시 증명하는 것 또는 (b) 정렬된 객체 또는 잘못 정렬된 객체를 처리할 수 있는 더 크고 느린 코드를 생성하는 것 중 하나를 필요로 합니다.
gcc 버그 보고서를 제출했습니다.말씀드렸듯이, 수정하는 것이 실용적이라고는 생각하지 않습니다만, 문서에는 기재되어 있을 것입니다(현재는 기재되어 있지 않습니다).
업데이트: 2018-12-20 현재 이 버그는 FIXED로 표시되어 있습니다.패치는 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;
| ^~~~~~~~~
한 바와 같이 ams의 이것은 단순히 불장난이다.라고 말할 때__attribute__((__packed__))
★★★★★★★★★★★★★★★★★」#pragma pack(1)
'''는 '''이래''는 '''이래''가 아니라 '''이래''입니다그렇지 않은 것으로 판명되면 컴파일러를 비난할 수 없습니다.
아마 우리는 컴파일러가 안주하고 있다고 비난할 수 있을 것이다.에는 gcc가 -Wcast-align
하게 되어 않습니다.디폴트로는 활성화되지 않습니다.-Wall
★★★★★★★★★★★★★★★★★」-Wextra
이것은 분명히 gcc 개발자들이 이런 종류의 코드를 대처할 가치가 없는 뇌사 상태의 "아모메이션"이라고 생각하기 때문인 것 같습니다.이러한 경멸은 이해할 수 있지만, 경험이 없는 프로그래머가 우연히 발견했을 때는 도움이 되지 않습니다.
다음 사항을 고려하십시오.
struct __attribute__((__packed__)) my_struct {
char c;
int i;
};
struct my_struct a = {'a', 123};
struct my_struct *b = &a;
int c = a.i;
int d = b->i;
int *e __attribute__((aligned(1))) = &a.i;
int *f = &a.i;
여서 of 의 종류,a
는 (위에서 정의한 바와 같이) 패킹된 구조체입니다.similarly유 similarly,, similarly 、b
이치노 " " " "a.i
는 1바이트 얼라인먼트의 int l-value 입니다. c
★★★★★★★★★★★★★★★★★」d
다 int
s. 을 읽을 a.i
컴파일러는 비정렬 액세스용 코드를 생성합니다.을을 때b->i
,b
님의 타입은 아직 만원임을 알고 있기 때문에 그쪽도 문제 없습니다. e
는 1바이트로 정렬된 int에 대한 포인터이므로 컴파일러는 이를 올바르게 참조 해제하는 방법도 알고 있습니다. 이 를 할 f = &a.i
정렬되지 않은 int 포인터의 값을 정렬된 int 포인터 변수에 저장하고 있습니다.그것이 바로 잘못된 것입니다.동의해요, gcc는 이 경고를 디폴트로 유효하게 할 필요가 있습니다(이 경고는-Wall
★★★★★★★★★★★★★★★★★」-Wextra
를 참조해 주세요.
이 속성을 사용하는 것은 절대 안전하지 않습니다.
한 가지 은 '아까의 입니다.union
구조체에 공통적인 초기 멤버 시퀀스가 있는 경우, 하나의 멤버를 쓰고 다른 멤버를 읽기 위한 두 개 이상의 구조가 포함됩니다.C11 표준 섹션 6.5.2.3은 다음과 같이 기술되어 있다.
6 조합의 사용을 단순화하기 위해 하나의 특별한 보증이 이루어진다. 조합이 공통의 초기 순서를 공유하는 여러 개의 구조(아래 참조)를 포함하고 조합 객체가 현재 이러한 구조 중 하나를 포함하고 있는 경우, 조합의 완료된 유형의 선언이 있는 모든 곳에서 조합의 공통 초기 부분을 검사할 수 있다.e연합이 표시된다.대응하는 멤버가 1개 또는 복수의 초기 멤버의 시퀀스에 대응하는 타입(비트필드에서는 같은 폭)을 가지는 경우, 구조체는 공통의 초기 시퀀스를 공유합니다.
...
9 예 3 다음은 유효한 단편입니다.
union { struct { int alltypes; }n; struct { int type; int intnode; } ni; struct { int type; double doublenode; } nf; }u; u.nf.type = 1; u.nf.doublenode = 3.14; /* ... */ if (u.n.alltypes == 1) if (sin(u.nf.doublenode) == 0.0) /* ... */
__attribute__((packed))
도입하면 이 문제를 해결할 수 있습니다.다음 예시는 최적화를 디세블로 한 상태에서 gcc 5.4.0을 사용하여 Ubuntu 16.04 x64에서 실행되었습니다.
#include <stdio.h>
#include <stdlib.h>
struct s1
{
short a;
int b;
} __attribute__((packed));
struct s2
{
short a;
int b;
};
union su {
struct s1 x;
struct s2 y;
};
int main()
{
union su s;
s.x.a = 0x1234;
s.x.b = 0x56789abc;
printf("sizeof s1 = %zu, sizeof s2 = %zu\n", sizeof(struct s1), sizeof(struct s2));
printf("s.y.a=%hx, s.y.b=%x\n", s.y.a, s.y.b);
return 0;
}
출력:
sizeof s1 = 6, sizeof s2 = 8
s.y.a=1234, s.y.b=5678
그럼에도 불구하고.struct s1
★★★★★★★★★★★★★★★★★」struct s2
"공통 초기 시퀀스"를 가지며, 전자에 적용되는 패킹은 대응하는 부재가 동일한 바이트 오프셋에서 살지 않음을 의미합니다. 값입니다.x.b
.y.b
표준에는 동일해야 한다고 되어 있는데도 말이다.
이 .
(점) (점)->
표기법
안전하지 않은 것은 정렬되지 않은 데이터의 포인터를 가져다가 그것을 고려하지 않고 액세스하는 것입니다.
또한 구조 내의 각 항목이 정렬되지 않은 것으로 알려져 있지만 특정 방식으로 정렬되지 않은 것으로 알려져 있기 때문에 컴파일러가 예상하는 대로 구조 전체가 정렬되지 않으면 문제가 발생합니다(일부 플랫폼에서 또는 향후 정렬되지 않은 액세스를 최적화하는 새로운 방법이 개발될 경우).
(다음은 설명을 위해 만든 매우 인위적인 예입니다.)패킹된 구조의 주요 용도 중 하나는 의미를 부여하는 데이터 스트림(예를 들어 256바이트)이 있는 경우입니다.작은 예를 들어 Arduino에서 실행 중인 프로그램이 시리얼을 통해 16바이트의 패킷을 전송한다고 가정해 보겠습니다.이 패킷은 다음과 같습니다.
0: message type (1 byte)
1: target address, MSB
2: target address, LSB
3: data (chars)
...
F: checksum (1 byte)
그럼 내가 신고할 수 있는 건
typedef struct {
uint8_t msgType;
uint16_t targetAddr; // may have to bswap
uint8_t data[12];
uint8_t checksum;
} __attribute__((packed)) myStruct;
포인터 산술이 아닌 aStruct.targetAddr을 통해 targetAddr 바이트를 참조할 수 있습니다.
얼라인먼트가 발생하고 있기 때문에 수신된 데이터에 대해 메모리 내의 보이드* 포인터를 가져와 myStruct*에 캐스팅하는 것은 컴파일러가 구조체를 packed로 취급하지 않는 한 기능하지 않습니다(즉, 지정된 순서대로 데이터를 저장하고 이 예에서는 정확히 16바이트를 사용합니다).정렬되지 않은 읽기에는 성능 저하가 따르기 때문에 프로그램에서 활발하게 작업 중인 데이터에 대해 패킹된 구조를 사용하는 것이 반드시 좋은 생각은 아닙니다.그러나 프로그램이 바이트 목록과 함께 제공된 경우, 팩된 구조를 통해 컨텐츠에 액세스하는 프로그램을 쉽게 작성할 수 있습니다.
그렇지 않으면 C++를 사용하여 백그라운드에서 포인터 계산을 수행하는 액세스 메서드와 같은 방법으로 클래스를 작성합니다.즉, 패킹된 구조는 패킹된 데이터를 효율적으로 처리하기 위한 것이며, 패킹된 데이터는 프로그램에서 사용할 수 있는 데이터일 수 있습니다.대부분의 경우 코드를 사용하여 구조에서 값을 읽고 작업을 수행한 후 값을 다시 써야 합니다.다른 모든 작업은 포장된 구조물 밖에서 수행해야 합니다.문제의 일부는 C가 프로그래머에게 숨기려고 하는 낮은 수준의 물건과 그러한 것들이 프로그래머에게 정말로 중요한 경우 필요한 후프 점프이다. (당신은 거의 다른 '데이터 레이아웃' 구조가 필요하기 때문에 당신은 '이것이 48바이트이다, foo는 13바이트의 데이터를 참조하고, 해석해야 한다.'앨리스와 밥이라는 두 개의 int와 캐롤이라는 플로트를 포함하는 구조를 원합니다'라고 하는 별도의 구조화된 데이터 구성. C에서는 이 두 가지 사용 사례가 모두 구조 구조에 구두주걱으로 묶여 있습니다.)
언급URL : https://stackoverflow.com/questions/8568432/is-gccs-attribute-packed-pragma-pack-unsafe
'programing' 카테고리의 다른 글
Vue 사용에 대한 v-의 알 수 없는 속성 (0) | 2022.08.21 |
---|---|
옛날에는 >가 <...>보다 빨랐을 때무엇이 기다리니? (0) | 2022.08.21 |
소켓 접속과 읽기 타임아웃의 차이점은 무엇입니까? (0) | 2022.08.21 |
C++ 규격에 따라 합법인지 아닌지를 나타내는 첨자를 사용하여 단일 어레이 요소의 주소를 취득할 수 있습니까? (0) | 2022.08.21 |
TypeScript를 사용할 때 단일 파일 구성 요소에서 Vue를 가져오는 방법 (0) | 2022.08.21 |