#pragma pack 효과


233

누군가가 #pragma pack전 처리기 명령문이 무엇을하는지, 더 중요하게는 왜 그것을 사용하기를 원하는지 설명 할 수 있는지 궁금 했습니다.

나는 약간의 통찰력을 제공 하는 MSDN 페이지를 체크 아웃 했지만 경험이있는 사람들로부터 더 많은 것을 듣기를 희망했습니다. 더 이상 어디에서 찾을 수 없지만 코드에서 이전에 보았습니다.


1
구조체의 특정 정렬 / 패킹을 강제하지만 모든 #pragma지시문 과 마찬가지로 구현 정의입니다.
dreamlax

A mod s = 0여기서 A는 주소이고 s는 데이터 유형의 크기입니다. 데이터가 잘못 정렬되지 않았는지 확인합니다.
legends2k

답변:


422

#pragma pack컴파일러에게 구조 멤버를 특정 정렬로 압축하도록 지시합니다. 구조체를 선언 할 때 대부분의 컴파일러는 멤버 사이에 패딩을 삽입하여 메모리의 적절한 주소 (일반적으로 유형 크기의 배수)에 정렬되도록합니다. 이렇게하면 올바르게 정렬되지 않은 변수에 액세스하는 것과 관련된 일부 아키텍처에서 성능 저하 (또는 명백한 오류)가 발생하지 않습니다. 예를 들어, 주어진 4 바이트 정수와 다음 구조체 :

struct Test
{
   char AA;
   int BB;
   char CC;
};

컴파일러는 다음과 같이 구조체를 메모리에 배치하도록 선택할 수 있습니다.

|   1   |   2   |   3   |   4   |  

| AA(1) | pad.................. |
| BB(1) | BB(2) | BB(3) | BB(4) | 
| CC(1) | pad.................. |

sizeof(Test)단지 16 바이트 데이터가 포함되어 있더라도, 4 × 3 = 12이 될 것이다. #pragma(내 지식으로는) 가장 일반적인 유스 케이스 는 컴파일러가 데이터에 패딩을 삽입하지 않고 각 멤버가 이전 것을 따르는 지 확인 해야하는 하드웨어 장치를 사용할 때입니다. 를 사용하면 #pragma pack(1)위의 구조체가 다음과 같이 배치됩니다.

|   1   |

| AA(1) |
| BB(1) |
| BB(2) |
| BB(3) |
| BB(4) |
| CC(1) |

그리고 sizeof(Test)1 × 6 = 6입니다.

를 사용하면 #pragma pack(2)위의 구조체가 다음과 같이 배치됩니다.

|   1   |   2   | 

| AA(1) | pad.. |
| BB(1) | BB(2) |
| BB(3) | BB(4) |
| CC(1) | pad.. |

그리고 sizeof(Test)2 × 4 = 8입니다.

구조체의 변수 순서도 중요합니다. 다음과 같이 순서가 지정된 변수가있는 경우

struct Test
{
   char AA;
   char CC;
   int BB;
};

와 함께 #pragma pack(2)구조체는 다음과 같이 배치됩니다.

|   1   |   2   | 

| AA(1) | CC(1) |
| BB(1) | BB(2) |
| BB(3) | BB(4) |

그리고 sizeOf(Test)3 × 2 = 6 일 것이다.


76
포장의 단점을 추가하는 것이 좋습니다. (정렬되지 않은 개체 액세스는 가장 느리지 만 일부 플랫폼에서는 오류가 발생합니다.)
jalf

11
언급 된 정렬 "성능 패널티"는 실제로 일부 시스템에서 이점이 될 수 있습니다 danluu.com/3c-conflict .

6
@Pacerier 실제로는 아닙니다. 그 포스트는 상당히 극단적 인 정렬 (4KB 경계에 정렬)에 대해 이야기합니다. CPU는 다양한 데이터 유형에 대해 특정 최소 정렬을 예상하지만 최악의 경우 8 바이트 정렬이 필요합니다 (16 또는 32 바이트 정렬이 필요할 수있는 벡터 유형은 계산하지 않음). 이러한 경계에 정렬하지 않으면 일반적으로 부하가 한 번이 아닌 두 번의 작업으로 수행 될 수 있기 때문에 눈에 띄는 성능 저하가 발생하지만 유형이 잘 정렬되어 있거나 맞지 않습니다. 엄격한 그 구매 당신에게없는 것보다는 정렬 (및 유적 캐시 사용
jalf

6
즉, double은 8 바이트 경계에있을 것으로 예상합니다. 7 바이트 경계에 배치하면 성능이 저하됩니다. 그러나 16, 32, 64 또는 4096 바이트 경계에 놓으면 8 바이트 경계가 이미 준 것보다 아무것도 사지 않습니다. CPU에서 동일한 성능을 얻을 수 있지만 해당 게시물에 요약 된 이유로 캐시 활용도가 훨씬 떨어집니다.
jalf

4
따라서 교훈은 "포장이 유익하다"는 것이 아니라 (포장은 유형의 자연적인 정렬을 위반하여 성능을 저하시킵니다), 단순히 "필요한 것 이상으로 지나치게 정렬하지 마십시오"
jalf

27

#pragma이식 불가능한 (이 컴파일러에서만) 메시지를 컴파일러에 전송하는 데 사용됩니다. 특정 경고 비활성화 및 구조체 패킹과 같은 것이 일반적인 이유입니다. 특정 경고를 비활성화하면 오류 플래그가 설정된 경고를 컴파일 할 때 특히 유용합니다.

#pragma pack특히 패킹되는 구조체의 멤버가 정렬되지 않아야 함을 나타내는 데 사용됩니다. 하나의 하드웨어에 메모리 매핑 인터페이스가 있고 다른 구조체 멤버가 가리키는 위치를 정확하게 제어 할 수 있어야 할 때 유용합니다. 대부분의 머신이 정렬 된 데이터를 처리하는 데 훨씬 빠르기 때문에 속도 최적화가 그리 좋지 않습니다.


17
나중에 취소하려면 #pragma pack (push, 1) 및 #pragma pack (pop)
malhal

16

컴파일러에게 구조체의 객체를 정렬 할 경계를 알려줍니다. 예를 들어 다음과 같은 것이 있다면

struct foo { 
    char a;
    int b;
};

일반적인 32 비트 컴퓨터를 사용하면 일반적으로 "할"것 사이의 패딩의 3 바이트 갖도록 a하고 b그 때문에 b자사의 액세스 속도를 극대화하기 위해 4 바이트 경계에 착륙합니다 (일반적으로 기본적으로 무슨 일이 일어날 지의 그).

그러나 외부에서 정의 된 구조와 일치해야하는 경우 컴파일러가 해당 외부 정의에 따라 구조를 정확하게 배치하도록합니다. 이 경우, 당신은 컴파일러 줄 수 #pragma pack(1)그것을 말할 수 없는 구성원 간의 패딩을 삽입을 - 구조의 정의는 구성원 간의 패딩을 포함하는 경우, 당신은 (명시 적으로 삽입 예를 들면, 일반적으로 명명 된 회원 unusedN이나 ignoreN, 또는에 뭔가 주문).


"일반적으로 a와 b 사이에 3 바이트의 패딩을 갖고"b "가 액세스 속도를 최대화하기 위해 4 바이트 경계에 놓이게하려고합니다."-3 바이트의 패딩을 사용하면 액세스 속도를 어떻게 극대화 할 수 있습니까?
Ashwin

8
@Ashwin : b4 바이트 경계에 배치 하면 프로세서가 단일 4 바이트로드를 실행하여로드 할 수 있습니다. 프로세서에 따라 다소 차이가 있지만 홀수 범위에 있으면로드 할 때 프로세서에 두 개의 별도로드 명령을 실행 한 다음 시프터를 사용하여 해당 조각을 모을 가능성이 큽니다. 일반적으로 벌점은 해당 항목의로드가 3 배 정도 느립니다.
Jerry Coffin

... aligned 및 unaligned int를 읽는 어셈블리 코드를 보면 정렬 된 읽기는 일반적으로 단일 니모닉입니다. 정렬되지 않은 읽기는 int를 함께 조각화하여 바이트 단위로 선택하고 레지스터의 올바른 위치에 배치 할 때 10 개의 어셈블리 라인 일 수 있습니다.
SF.

2
@SF .: x86 CPU에서 (실제로 예를 들자면) 작업이 하드웨어에서 수행되지만 여전히 동일한 작업 집합을 얻을 수 있습니다. 둔화.
Jerry Coffin

8

데이터 요소 (예 : 클래스 및 구조체의 멤버)는 일반적으로 액세스 시간을 향상시키기 위해 현재 세대 프로세서의 WORD 또는 DWORD 경계에 정렬됩니다. 4로 나눌 수없는 주소에서 DWORD를 검색하려면 32 비트 프로세서에서 하나 이상의 추가 CPU주기가 필요합니다. 따라서 예를 들어 char 멤버가 세 개인 경우 char a, b, c;실제로 6 또는 12 바이트의 스토리지를 사용하는 경향이 있습니다.

#pragma액세스 속도를 희생하거나 다른 컴파일러 대상간에 저장된 데이터의 일관성을 유지하면서보다 효율적인 공간 사용을 달성하기 위해이를 무시할 수 있습니다. 16 비트에서 32 비트 코드로의 전환에 많은 재미가있었습니다. 64 비트 코드로 포팅하면 일부 코드에서 같은 종류의 두통이 발생할 것으로 예상됩니다.


실제로, char a,b,c;보통 3 바이트 또는 4 바이트의 스토리지 (최소 x86에서)가 필요합니다. 즉, 정렬 요구 사항이 1 바이트이기 때문입니다. 그렇지 않다면 어떻게 처리 char str[] = "foo";하겠습니까? 에 대한 액세스 char는 항상 간단한 페치 시프트 마스크이며, int정렬 여부에 따라 페치 페치 병합 또는 페치에 대한 액세스가 가능합니다. int반 그렇지 않으면 당신이 (말) 얻을 것 때문에 32 비트 (4 바이트) 정렬 (x86에서)이 int하나에서 DWORD다른과 절반, 그리고 두 조회를 취할 것입니다.
Tim Čas

3

컴파일러는 특정 플랫폼에서 최대 성능을 달성하기 위해 구조의 멤버를 정렬 할 수 있습니다. #pragma pack지시문을 사용하면 해당 정렬을 제어 할 수 있습니다. 일반적으로 최적의 성능을 위해서는 기본적으로 그대로 두어야합니다. 원격 시스템에 구조를 전달해야하는 경우 일반적으로 #pragma pack 1원치 않는 정렬을 제외하는 데 사용 됩니다.


2

컴파일러 특정 아키텍처에서 성능상의 이유로 특정 바이트 경계에 구조 멤버를 배치 할 수 있습니다 . 이렇게하면 멤버간에 사용되지 않은 패딩이 남을 수 있습니다. 구조 패킹은 멤버가 연속되도록합니다.

예를 들어 데이터가 필요한 데이터가 시퀀스 내 특정 위치에있는 특정 파일 또는 통신 형식을 준수하는 구조가 필요한 경우에 중요 할 수 있습니다. 그러나 이러한 사용법은 엔디안 문제를 다루지 않으므로 사용되었지만 휴대용이 아닐 수 있습니다.

또한 예를 들어 UART 또는 USB 컨트롤러와 같은 일부 I / O 장치의 내부 레지스터 구조를 정확하게 오버레이하여 레지스터 액세스가 직접 주소가 아닌 구조를 통해 이루어질 수 있습니다.


1

레지스터 순서 및 정렬에 대한 엄격한 요구 사항이있는 일부 하드웨어 (예 : 메모리 매핑 된 장치)로 코딩하는 경우에만이 기능을 사용하려고합니다.

그러나 이것은 그 목표를 달성하기위한 꽤 무딘 도구처럼 보입니다. 더 나은 접근 방법은 미니 드라이버를 어셈블러로 코딩하고이 pragma를 사용하는 대신 C 호출 인터페이스를 제공하는 것입니다.


실제로 자주 액세스하지 않는 큰 테이블의 공간을 절약하기 위해 실제로 많이 사용합니다. 공간을 절약하고 엄격하게 정렬하지는 않습니다. (방금 당신을 투표했습니다. btw. 누군가가 당신에게 부정적인 투표를주었습니다.)
Todd Lehman

1

레거시 코드와 인터페이스하기 위해 이전에 코드에서 사용했습니다. 이것은 이전의 카본 버전 (원래 M68k 시스템 6.5 버전과 호환이 되었음)을 통해 기본 설정 파일을로드해야하는 Mac OS X Cocoa 응용 프로그램입니다. 원래 버전의 환경 설정 파일은 구성 구조의 이진 덤프로, #pragma pack(1)여분의 공간을 차지하고 정크를 절약 하는 데 사용 되었습니다 (즉, 구조에있는 패딩 바이트).

코드의 원래 작성자는 #pragma pack(1)프로세스 간 통신에서 메시지로 사용 된 구조를 저장 하는 데에도 사용 되었습니다. 코드가 때로는 시작부터 바이트 수를 세어 메시지 구조체의 특정 부분을 보았으므로 알 수 없거나 변경 된 패딩 크기의 가능성을 피하기위한 이유는 여기에서 생각합니다 (ewww).


1

사람들이 다중 스레드 컨텍스트에서 허위 공유를 방지하기 위해 구조가 전체 캐시 라인을 사용하도록하기 위해이를 사용하는 것을 보았습니다. 정렬되지 않은 메모리 액세스는 일반적으로 속도가 느려져 단점이있을 수 있지만 기본적으로 느슨하게 압축 될 많은 수의 객체가있을 경우 메모리를 절약하고 캐시 성능을 향상시켜 더 단단히 압축 할 수 있습니다.


0

#pragma pack이 제공하는 데이터 일관성을 달성하는 다른 방법이 있습니다 (예를 들어 일부 사람들은 네트워크를 통해 전송해야하는 구조에 #pragma pack (1)을 사용함). 예를 들어, 다음 코드 및 후속 출력을 참조하십시오.

#include <stdio.h>

struct a {
    char one;
    char two[2];
    char eight[8];
    char four[4];
};

struct b { 
    char one;
    short two;
    long int eight;
    int four;
};

int main(int argc, char** argv) {
    struct a twoa[2] = {}; 
    struct b twob[2] = {}; 
    printf("sizeof(struct a): %i, sizeof(struct b): %i\n", sizeof(struct a), sizeof(struct b));
    printf("sizeof(twoa): %i, sizeof(twob): %i\n", sizeof(twoa), sizeof(twob));
}

출력은 다음과 같습니다. sizeof (struct a) : 15, sizeof (struct b) : 24 sizeof (twoa) : 30, sizeof (twob) : 48

구조체 a의 크기가 바이트 수와 정확히 일치하지만 구조체 b에 패딩이 추가되었습니다 (패딩에 대한 자세한 내용 은 내용 참조 ). #pragma 팩과 달리이 작업을 수행하면 "와이어 형식"을 적절한 유형으로 변환 할 수 있습니다. 예를 들어, "char two [2]"는 "short int"등으로 입력됩니다.


아뇨. b.two의 메모리에서 위치를 보면 b.one 뒤의 1 바이트가 아닙니다 (컴파일러는 b.two를 정렬하여 단어 액세스에 정렬 할 수 있습니다). a.2의 경우 a.one 다음에 정확히 1 바이트입니다. 짧은 int로 a.two에 액세스 해야하는 경우 2 가지 대안이 있어야합니다. 연합을 사용하거나 (엔디안 문제가있는 경우 일반적으로 실패합니다) 코드로 압축을 풀거나 변환합니다 (적절한 ntohX 함수 사용)
xryl669

1
sizeof반환 size_t되는 사용하여 인쇄 할 수 있어야합니다%zu . 잘못된 형식 지정자를 사용하면 정의되지 않은 동작이 발생합니다
phuclv

0

왜 그것을 사용하고 싶습니까?

구조의 메모리를 줄이려면

왜 그것을 사용해서는 안됩니까?

  1. 일부 시스템은 정렬 된 데이터에서 더 잘 작동하기 때문에 성능이 저하 될 수 있습니다.
  2. 일부 기계는 정렬되지 않은 데이터를 읽지 못합니다
  3. 코드는 이식성이 없다
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.