구조체의 sizeof가 각 멤버의 sizeof의 합과 같지 않은 이유는 무엇입니까?

StackOverflow https://stackoverflow.com/questions/119123

문제

sizeof 연산자는 구조 멤버의 전체 크기보다 더 큰 구조 크기를 반환합니까?

도움이 되었습니까?

해결책

이는 정렬 제약 조건을 충족하기 위해 추가된 패딩 때문입니다. 데이터 구조 정렬 프로그램의 성능과 정확성 모두에 영향을 미칩니다.

  • 잘못 정렬된 액세스는 심각한 오류일 수 있습니다(종종 SIGBUS).
  • 잘못 정렬된 액세스는 소프트 오류일 수 있습니다.
    • 약간의 성능 저하를 위해 하드웨어에서 수정되었습니다.
    • 또는 심각한 성능 저하의 경우 소프트웨어의 에뮬레이션을 통해 수정됩니다.
    • 또한 원자성 및 기타 동시성 보장이 깨져 미묘한 오류가 발생할 수 있습니다.

다음은 x86 프로세서(모두 32비트 및 64비트 모드 사용)에 대한 일반적인 설정을 사용하는 예입니다.

struct X
{
    short s; /* 2 bytes */
             /* 2 padding bytes */
    int   i; /* 4 bytes */
    char  c; /* 1 byte */
             /* 3 padding bytes */
};

struct Y
{
    int   i; /* 4 bytes */
    char  c; /* 1 byte */
             /* 1 padding byte */
    short s; /* 2 bytes */
};

struct Z
{
    int   i; /* 4 bytes */
    short s; /* 2 bytes */
    char  c; /* 1 byte */
             /* 1 padding byte */
};

const int sizeX = sizeof(struct X); /* = 12 */
const int sizeY = sizeof(struct Y); /* = 8 */
const int sizeZ = sizeof(struct Z); /* = 8 */

멤버를 정렬별로 정렬하여 구조의 크기를 최소화할 수 있습니다(기본 유형에서는 크기별로 정렬하면 충분함)(구조와 유사). Z 위의 예에서).

중요 사항:C 및 C++ 표준에서는 구조 정렬이 구현에 따라 정의된다고 명시합니다.따라서 각 컴파일러는 데이터를 다르게 정렬하도록 선택하여 서로 다르고 호환되지 않는 데이터 레이아웃을 초래할 수 있습니다.이러한 이유로 다른 컴파일러에서 사용되는 라이브러리를 처리할 때 컴파일러가 데이터를 정렬하는 방법을 이해하는 것이 중요합니다.일부 컴파일러에는 명령줄 설정 및/또는 특수 기능이 있습니다. #pragma 구조 정렬 설정을 변경하는 명령문입니다.

다른 팁

C FAQ에 설명된 패킹 및 바이트 정렬 여기:

정렬을 위한 것입니다.많은 프로세서는 2 바이트 및 4 바이트 수량에 액세스 할 수 없습니다 (예 :ints and long int) 그들이 모든 곳에서 삐걱 거리는 경우.

다음과 같은 구조가 있다고 가정합니다.

struct {
    char a[3];
    short int b;
    long int c;
    char d[3];
};

이제이 구조물을 다음과 같이 메모리에 포장하는 것이 가능하다고 생각할 수도 있습니다.

+-------+-------+-------+-------+
|           a           |   b   |
+-------+-------+-------+-------+
|   b   |           c           |
+-------+-------+-------+-------+
|   c   |           d           |
+-------+-------+-------+-------+

그러나 컴파일러가 다음과 같이 배열하면 프로세서에서 훨씬 쉽습니다.

+-------+-------+-------+
|           a           |
+-------+-------+-------+
|       b       |
+-------+-------+-------+-------+
|               c               |
+-------+-------+-------+-------+
|           d           |
+-------+-------+-------+

포장 된 버전에서는 B와 C 필드가 어떻게 감싸는 지 보는 것이 당신과 나에게 적어도 조금 어려운지 주목하십시오.간단히 말해서 프로세서도 어렵습니다.따라서 대부분의 컴파일러는 다음과 같이 구조물을 추가로 배치합니다.

+-------+-------+-------+-------+
|           a           | pad1  |
+-------+-------+-------+-------+
|       b       |     pad2      |
+-------+-------+-------+-------+
|               c               |
+-------+-------+-------+-------+
|           d           | pad3  |
+-------+-------+-------+-------+

예를 들어 GCC를 사용하여 구조가 특정 크기를 갖기를 원한다면 __attribute__((packed)).

Windows에서는 다음과 함께 cl.exe 컴파일러를 사용할 때 정렬을 1바이트로 설정할 수 있습니다. /Zp 옵션.

일반적으로 플랫폼과 컴파일러에 따라 CPU가 4(또는 8)의 배수인 데이터에 액세스하는 것이 더 쉽습니다.

따라서 기본적으로 정렬의 문제입니다.

바꾸려면 타당한 이유가 있어야 합니다.

이는 구조가 플랫폼에서 짝수 바이트(또는 단어)로 나오도록 하는 바이트 정렬 및 패딩 때문일 수 있습니다.예를 들어 Linux의 C에서는 다음 3가지 구조가 있습니다.

#include "stdio.h"


struct oneInt {
  int x;
};

struct twoInts {
  int x;
  int y;
};

struct someBits {
  int x:2;
  int y:6;
};


int main (int argc, char** argv) {
  printf("oneInt=%zu\n",sizeof(struct oneInt));
  printf("twoInts=%zu\n",sizeof(struct twoInts));
  printf("someBits=%zu\n",sizeof(struct someBits));
  return 0;
}

크기(바이트)가 각각 4바이트(32비트), 8바이트(2x 32비트) 및 1바이트(2+6비트)인 멤버가 있습니다.위 프로그램(Linux에서 gcc를 사용)은 크기를 4, 8, 4로 인쇄합니다. 여기서 마지막 구조는 단일 단어(내 32비트 플랫폼에서는 4 x 8비트 바이트)가 되도록 채워집니다.

oneInt=4
twoInts=8
someBits=4

또한보십시오:

Microsoft Visual C의 경우:

http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx

GCC는 Microsoft 컴파일러와의 호환성을 주장합니다.:

http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html

이전 답변 외에도 포장 여부에 관계없이 참고하시기 바랍니다. C++에는 회원 주문 보장이 없습니다..컴파일러는 가상 테이블 포인터와 기본 구조의 멤버를 구조에 추가할 수 있습니다.표준에서는 가상 테이블의 존재조차 보장하지 않으므로(가상 메커니즘 구현이 지정되지 않음) 이러한 보장은 불가능하다고 결론 내릴 수 있습니다.

나는 확신한다 회원 주문 ~이다 C에서는 보장됨, 그러나 크로스 플랫폼이나 크로스 컴파일러 프로그램을 작성할 때 나는 그것에 의존하지 않을 것입니다.

패킹이라고 불리는 것 때문에 구조의 크기는 부품의 합보다 큽니다.특정 프로세서에는 작동하는 기본 데이터 크기가 있습니다.대부분의 최신 프로세서가 선호하는 크기는 32비트(4바이트)입니다.데이터가 이러한 종류의 경계에 있을 때 메모리에 액세스하는 것은 해당 크기 경계에 걸쳐 있는 것보다 더 효율적입니다.

예를 들어.간단한 구조를 고려하십시오.

struct myStruct
{
   int a;
   char b;
   int c;
} data;

머신이 32비트 머신이고 데이터가 32비트 경계에 정렬된 경우 즉각적인 문제가 발생합니다(구조 정렬이 없다고 가정).이 예에서는 구조 데이터가 주소 1024(0x400 - 가장 낮은 2비트가 0이므로 데이터가 32비트 경계에 정렬됨)에서 시작한다고 가정합니다.data.a에 대한 액세스는 경계(0x400)에서 시작하므로 제대로 작동합니다.data.b에 대한 액세스도 주소 0x404(또 다른 32비트 경계)에 있기 때문에 잘 작동합니다.그러나 정렬되지 않은 구조는 data.c를 주소 0x405에 넣습니다.data.c의 4바이트는 0x405, 0x406, 0x407, 0x408에 있습니다.32비트 시스템에서 시스템은 한 메모리 주기 동안 data.c를 읽지만 4바이트 중 3바이트만 가져옵니다(4번째 바이트는 다음 경계에 있음).따라서 시스템은 4번째 바이트를 얻기 위해 두 번째 메모리 액세스를 수행해야 합니다.

이제 data.c를 주소 0x405에 넣는 대신 컴파일러가 구조를 3바이트만큼 채우고 data.c를 주소 0x408에 넣으면 시스템은 데이터를 읽는 데 1사이클만 필요하므로 해당 데이터 요소에 대한 액세스 시간이 단축됩니다. 50%.패딩은 처리 효율성을 위해 메모리 효율성을 바꿉니다.컴퓨터가 엄청난 양의 메모리(수 기가바이트)를 가질 수 있다는 점을 감안할 때 컴파일러는 스왑(크기 대비 속도)이 합리적인 것이라고 생각합니다.

불행하게도 이 문제는 네트워크를 통해 구조를 보내거나 바이너리 데이터를 바이너리 파일에 쓰려고 할 때 치명적인 문제가 됩니다.구조나 클래스의 요소 사이에 삽입된 패딩은 파일이나 네트워크로 전송되는 데이터를 방해할 수 있습니다.이식 가능한 코드(여러 다른 컴파일러로 이동되는 코드)를 작성하려면 적절한 "패킹"을 보장하기 위해 구조의 각 요소에 개별적으로 액세스해야 할 것입니다.

반면, 컴파일러마다 데이터 구조 패킹을 관리하는 기능이 다릅니다.예를 들어 Visual C/C++에서 컴파일러는 #pragma pack 명령을 지원합니다.이를 통해 데이터 패킹 및 정렬을 조정할 수 있습니다.

예를 들어:

#pragma pack 1
struct MyStruct
{
    int a;
    char b;
    int c;
    short d;
} myData;

I = sizeof(myData);

이제 길이는 11이 됩니다.pragma가 없으면 컴파일러의 기본 패킹에 따라 11에서 14(일부 시스템의 경우 최대 32)가 될 수 있습니다.

암시적으로 또는 명시적으로 구조체의 정렬을 설정한 경우 그렇게 할 수 있습니다.4로 정렬된 구조체는 해당 멤버의 크기가 4바이트의 배수가 아닌 경우에도 항상 4바이트의 배수가 됩니다.

또한 라이브러리는 32비트 정수를 사용하여 x86에서 컴파일될 수 있으며 64비트 프로세스에서 해당 구성 요소를 비교하면 이 작업을 직접 수행할 경우 다른 결과가 나올 수 있습니다.

C99 N1256 표준 초안

http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf

6.5.3.4 sizeof 연산자:

3 구조 또는 결합 유형을 갖는 피연산자에 적용될 때, 결과는 내부 및 후행 패딩을 포함하여 이러한 객체의 총 바이트 수입니다.

6.7.2.1 구조 및 공용체 지정자:

13 ...구조 객체 내에 이름이없는 패딩이있을 수 있지만 처음에는 그렇지 않습니다.

그리고:

15 구조체나 공용체 끝에 이름 없는 패딩이 있을 수 있습니다.

새로운 C99 유연한 배열 멤버 기능 (struct S {int is[];};) 패딩에도 영향을 미칠 수 있습니다.

16 특별한 경우, 멤버가 둘 이상인 구조의 마지막 요소는 불완전한 배열 유형을 가질 수 있습니다.이를 유연한 배열 멤버라고 합니다.대부분의 상황에서 유연한 배열 멤버는 무시됩니다.특히, 구조의 크기는 마치 생략이 암시하는 것보다 더 많은 후행 패딩을 가질 수 있다는 점을 제외하고 유연한 배열 부재가 생략 된 것처럼 보인다.

부록 J 이식성 문제 반복한다:

다음은 지정되지 않았습니다....

  • 구조체 또는 공용체에 값을 저장할 때 패딩 바이트 값(6.2.6.1)

C++11 N3337 표준 초안

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

5.3.3 크기:

2 클래스에 적용될 때 결과는 해당 유형의 객체를 배열에 배치하는 데 필요한 패딩을 포함하여 해당 클래스의 객체의 바이트 수입니다.

9.2 클래스 구성원:

ReneterPret_cast를 사용하여 적절하게 변환 된 표준 계층 구조 객체에 대한 포인터는 초기 멤버 (또는 해당 멤버가 비트 필드 인 경우, 그와 관련된 단위로)를 가리키며 그 반대도 마찬가지입니다.[ 메모:따라서 표준 층 구조 객체 내에 이름이없는 패딩이있을 수 있지만 적절한 정렬을 달성하는 데 필요한 경우 처음에는 그렇지 않을 수 있습니다.— 끝 참고 ]

나는 메모를 이해하기에 충분한 C++만을 알고 있습니다 :-)

다른 답변 외에도 구조체에는 가상 함수가 있을 수 있지만 일반적으로 그렇지 않습니다. 이 경우 구조체의 크기에는 vtbl 공간도 포함됩니다.

C 언어는 컴파일러에게 메모리의 구조적 요소 위치에 대해 어느 정도 자유를 줍니다.

  • 두 구성 요소 사이와 마지막 구성 요소 뒤에 메모리 구멍이 나타날 수 있습니다.이는 대상 컴퓨터의 특정 유형의 개체가 주소 지정 경계에 의해 제한될 수 있다는 사실 때문이었습니다.
  • sizeof 연산자의 결과에 "메모리 구멍" 크기가 포함됩니다.sizeof에는 C/C++에서 사용할 수 있는 유연한 배열의 크기만 포함되지 않습니다.
  • 일부 언어 구현에서는 pragma 및 컴파일러 옵션을 통해 구조의 메모리 레이아웃을 제어할 수 있습니다.

C 언어는 프로그래머에게 구조의 요소 레이아웃에 대한 어느 정도 보증을 제공합니다.

  • 메모리 주소를 증가시키는 일련의 구성 요소를 할당하는 데 필요한 컴파일러
  • 첫 번째 구성 요소의 주소는 구조의 시작 주소와 일치합니다.
  • 명명되지 않은 비트 필드는 인접한 요소의 필수 주소 정렬에 대한 구조에 포함될 수 있습니다.

요소 정렬과 관련된 문제:

  • 다양한 컴퓨터가 다양한 방식으로 개체의 가장자리에 선을 긋습니다.
  • 비트 필드 너비에 대한 다양한 제한 사항
  • 바이트를 단어로 저장하는 방법은 컴퓨터마다 다릅니다(Intel 80x86 및 Motorola 68000).

정렬 작동 방식:

  • 구조가 차지하는 부피는 그러한 구조 배열의 정렬된 단일 요소의 크기로 계산됩니다.다음 구조의 첫 번째 요소가 정렬 요구 사항을 위반하지 않도록 구조가 끝나야합니다.

p.s 자세한 내용은 여기에서 확인할 수 있습니다."Samuel P.Harbison, Guy L.Steele CA 참조, (5.6.2 - 5.6.7)"

속도와 캐시를 고려하여 피연산자를 원래 크기에 맞춰 정렬된 주소에서 읽어야 한다는 아이디어입니다.이를 수행하기 위해 컴파일러는 다음 멤버 또는 다음 구조체가 정렬되도록 구조체 멤버를 채웁니다.

struct pixel {
    unsigned char red;   // 0
    unsigned char green; // 1
    unsigned int alpha;  // 4 (gotta skip to an aligned offset)
    unsigned char blue;  // 8 (then skip 9 10 11)
};

// next offset: 12

x86 아키텍처는 항상 잘못 정렬된 주소를 가져올 수 있었습니다.그러나 속도가 더 느리고 잘못된 정렬이 두 개의 서로 다른 캐시 라인과 겹치면 정렬된 액세스가 하나만 제거할 때 두 개의 캐시 라인을 제거합니다.

일부 아키텍처는 실제로 잘못 정렬된 읽기 및 쓰기와 초기 버전의 ARM 아키텍처(현재의 모든 모바일 CPU로 발전한 아키텍처)를 잡아야 합니다.글쎄, 그들은 실제로 그에 대한 잘못된 데이터를 반환했습니다.(낮은 순서의 비트는 무시했습니다.)

마지막으로, 캐시 라인은 임의로 클 수 있으며 컴파일러는 이를 추측하거나 공간 대 속도 균형을 맞추려고 시도하지 않습니다.대신 정렬 결정은 ABI의 일부이며 결국 캐시 라인을 균등하게 채우는 최소 정렬을 나타냅니다.

요약: 정렬이 중요합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top