sizeof
연산자가 구조체의 전체 크기보다 구조체에 대해 더 큰 크기를 반환하는 이유는 무엇 입니까?
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
구조 정렬 설정을 변경하기위한 명령 줄 설정 및 / 또는 특수 문이 있습니다.
패킹 및 바이트 정렬은 FAQ C에 설명 된대로 여기서 :
정렬을위한 것입니다. 대부분의 프로세서는 2 바이트 및 4 바이트 수량 (예 : 정수 및 긴 정수)에 액세스 할 수 없습니다.
이 구조가 있다고 가정하십시오.
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 | +-------+-------+-------+-------+
s
then &s.a == &s
이고 &s.d == &s + 12
(답에 표시된 정렬이 제공됨). 포인터는 배열의 크기가 가변적 인 경우에만 저장 되지만 (예 : 대신에 a
선언 된 char a[]
경우 char a[3]
) 요소를 다른 곳에 저장해야합니다.
예를 들어 GCC를 사용하여 구조가 특정 크기를 갖도록하려면을 사용하십시오 __attribute__((packed))
.
Windows에서는 cl.exe compier를 / Zp 옵션 과 함께 사용할 때 정렬을 1 바이트로 설정할 수 있습니다 .
일반적으로 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 비트)입니다. 위의 프로그램 (gcc를 사용하는 Linux에서)은 4, 8 및 4로 크기를 인쇄합니다. 마지막 구조는 단일 단어 (32 비트 플랫폼에서 4 x 8 비트 바이트)가되도록 채워집니다.
oneInt=4
twoInts=8
someBits=4
:2
그리고 :6
실제로이 경우 전체 32 비트 정수가 아닌 2 및 6 비트를 지정합니다. someBits.x는 2 비트이므로 00, 01, 10 및 11 (1, 2, 3 및 4)의 4 가지 가능한 값만 저장할 수 있습니다. 이게 말이 되요? 이 기능에 대한 기사는 다음과 같습니다. geeksforgeeks.org/bit-fields-c
또한보십시오:
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에서 멤버 순서 가 보장된다고 확신 하지만 크로스 플랫폼 또는 크로스 컴파일러 프로그램을 작성할 때 그것을 믿을 수는 없습니다.
구조의 크기는 소위 패킹 (packing)으로 인해 부분의 합보다 큽니다. 특정 프로세서는 선호하는 데이터 크기를 가지고 있습니다. 32 비트 (4 바이트) 인 경우 최신 프로세서의 기본 크기입니다. 데이터가 이런 종류의 경계에있을 때 메모리에 액세스하는 것이 해당 크기 경계를 넘는 것보다 효율적입니다.
예를 들어. 간단한 구조를 고려하십시오.
struct myStruct
{
int a;
char b;
int c;
} data;
머신이 32 비트 머신이고 데이터가 32 비트 경계에 정렬되면 즉각적인 문제가 발생합니다 (구조 정렬이 없다고 가정). 이 예에서는 구조 데이터가 주소 1024에서 시작한다고 가정합니다 (0x400-가장 낮은 2 비트가 0이므로 데이터가 32 비트 경계에 정렬 됨). data.a에 대한 액세스는 경계-0x400에서 시작하기 때문에 제대로 작동합니다. data.b에 대한 액세스는 또 다른 32 비트 경계인 주소 0x404에 있기 때문에 제대로 작동합니다. 그러나 정렬되지 않은 구조는 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까지) 될 수 있습니다.
#pragma pack
. 멤버가 기본 정렬에 할당되면 일반적으로 구조가 패킹 되지 않았다고 말합니다 .
C99 N1256 표준 초안
http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
6.5.3.4 연산자의 크기 :
3 구조 또는 공용체 유형을 갖는 피연산자에 적용되면 결과는 내부 및 후행 패딩을 포함하여 이러한 오브젝트의 총 바이트 수입니다.
6.7.2.1 구조 및 공용체 지정자 :
13 ... 구조 객체 내에 이름없는 패딩이있을 수 있지만 시작 부분에는 없습니다.
과:
15 구조체 나 공용체 끝에 이름없는 패딩이있을 수 있습니다.
새로운 C99 Flexible Array 멤버 기능 ( struct S {int is[];};
)도 패딩에 영향을 줄 수 있습니다.
특별한 경우, 하나 이상의 명명 된 멤버가있는 구조의 마지막 요소는 불완전한 배열 유형을 가질 수 있습니다. 이것을 유연한 배열 구성원이라고합니다. 대부분의 경우 가변 배열 멤버는 무시됩니다. 특히, 구조의 크기는가요 성 어레이 부재가 생략되는 것보다 더 많은 후미 패딩을 가질 수 있다는 것을 제외하고는가요 성 어레이 부재가 생략 된 것과 같다.
부속서 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 반원 :
reinterpret_cast를 사용하여 적절히 변환 된 표준 레이아웃 구조체 객체에 대한 포인터는 초기 멤버 (또는 해당 멤버가 비트 필드 인 경우 해당 필드가있는 단위)를 가리키고 그 반대도 마찬가지입니다. [참고 : 표준 레이아웃 구조체 안에는 이름이없는 패딩이있을 수 있지만, 적절한 정렬을 위해 필요에 따라 시작 부분에는 없습니다. — 끝 참고]
나는 메모를 이해하기에 충분한 C ++ 만 알고 있습니다 :-)
다른 답변 외에도 구조체는 가상 함수를 가질 수 있지만 일반적으로 구조체의 크기는 vtbl의 공간을 포함합니다.
C 언어는 메모리에서 구조적 요소의 위치에 대해 컴파일러에게 약간의 자유를 남깁니다.
C 언어는 프로그래머가 구조의 요소 레이아웃을 확실하게 보장합니다.
요소 정렬과 관련된 문제 :
정렬 작동 방식 :
ps 더 자세한 정보는 여기에 있습니다 : "Samuel P.Harbison, Guy L.Steele CA Reference, (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의 일부이며 결국 캐시 라인을 균일하게 채울 최소 정렬을 나타냅니다.
TL; DR : 정렬이 중요합니다.