다른걸 찾아보다 재미난게 보여서 기록 해 둔다.
먼저 아래의 소스코드를 보고 다시 얘기하자.
- #include <stdio.h>
- #include <Windows.h>
- struct TEST1
- {
- char c;
- short s;
- int i;
- double d;
- };
- struct TEST2
- {
- short s;
- int i;
- double d;
- char c;
- };
- #pragma pack(1)
- struct TEST3
- {
- short s;
- int i;
- double d;
- char c;
- };
- #pragma pack()
- void main()
- {
- TEST1 T;
- TEST2 T2;
- TEST3 T3;
- printf( "====== T ======\n" );
- printf( "Sizeof(c) : %d\n", sizeof(T.c) );
- printf( "Sizeof(s) : %d\n", sizeof(T.s) );
- printf( "Sizeof(i) : %d\n", sizeof(T.i) );
- printf( "Sizeof(d) : %d\n", sizeof(T.d) );
- printf( "Sizeof(T) : %d\n", sizeof(T) );
- printf( "\n====== T2 ======\n" );
- printf( "Sizeof(c) : %d\n", sizeof(T2.c) );
- printf( "Sizeof(s) : %d\n", sizeof(T2.s) );
- printf( "Sizeof(i) : %d\n", sizeof(T2.i) );
- printf( "Sizeof(d) : %d\n", sizeof(T2.d) );
- printf( "Sizeof(T) : %d\n", sizeof(T2) );
- printf( "\n====== T3 ======\n" );
- printf( "Sizeof(c) : %d\n", sizeof(T3.c) );
- printf( "Sizeof(s) : %d\n", sizeof(T3.s) );
- printf( "Sizeof(i) : %d\n", sizeof(T3.i) );
- printf( "Sizeof(d) : %d\n", sizeof(T3.d) );
- printf( "Sizeof(T) : %d\n", sizeof(T3) );
- system("pause");
- }
위의 소스코드에서 사용된 구조체 TEST1, TEST2, TEST3 의 크기는 동일할까?
세개의 구조체 모두 각각 하나씩의 char, short, int, double 형의 변수들을 멤버로 가지고 있다.
다만 선언된 순서가 다를뿐 동일하게 구성되어 있으니 저 구조체들은 동일한 크기를 가질 것이다.
일단 TEST1 구조체와 TEST2구조체의 크기를 살펴 보자
결과를 보면 구조체의 멤버변수들의 크기는 모두가 알듯이 각각 1, 2, 4, 8 byte 의 크기를 가진다.
그렇다면 구조체의 크기는 당연히 1+2+4+8 = 15byte! 가 되야하는데 아니잖아!
이 이유는 프로젝트를 만들때 32비트용 프로그램으로 만들었기 떄문이다.
32비트, 즉 4바이트를 기본 크기로 갖는 프로그램이라는 얘기다.
이게 어떻게 영향을 미치는지는 아래를 보면 쉽게 이해가 된다.
먼저 TEST1 구조체를 살펴보면
변수들을 담아야 하니 메모리공간을 할당한다.
기본크기가 4바이트니까 4바이트 짜리 공간을 만들고 그 맨앞에 제일먼저 선언된 c를 채워넣는다.
[ [c] [] [] [] ]
그다음 선언된 s 를 채워넣고..
[ [c] [s] [s] [] ]
그다음의 i를 채워넣으려는데 기본크기에서 1바이트밖에 남지않아서 4바이트 크기의 int를 담을수가 없다.
그래서 기본크기인 4바이트 짜리 공간을 하나 더 만들고 거기에 i를 채워넣는다.
[ [c] [s] [s] [-] ] [ [i] [i] [i] [i] ]
마찬가지로 뒤에 d를 넣고 나면
[ [c] [s] [s] [-] ] [ [i] [i] [i] [i] ] [ [d] [d] [d] [d] ] [ [d] [d] [d] [d] ]
총 16바이트의 크기를 갖는 구조체가 된다.
자 그럼 이제 TEST2 구조체를 보자
마찬가지로 기본크기 4바이트 메모리 공간을 만들고, s를 채워 넣는다.
[ [s] [s] [] [] ]
그다음 i 를 넣으려는데 2바이트 뿐이네? 그럼 어떻게?? 새로 공간을 하나 더 만들지.
[ [s] [s] [-] [-] ] [ [i] [i] [i] [i] ]
그다음 마찬가지로 c 와 d 를 넣어보면
[ [s] [s] [-] [-] ] [ [i] [i] [i] [i] ] [ [d] [d] [d] [d] ] [ [d] [d] [d] [d] ] [ [c] [-] [-] [-] ]
20 바이트 짜리 구조체가 되어야 하는데 왜 24 바이트지.. 이건 공부해서 수정하도록..
이부분에 대한 보충설명은 아래에 덧붙임.
아직 다 이해가 되진 않지만 일단 이런식으로 메모리 할당이 되는데.
여기서 이런식으로 메모리를 낭비하고싶지 않다 할떄 쓰는게 TEST3 구조체 앞에있는
#pragma pack(n) 전처리 문이다.
32비트 프로그램이니 기본크기가 4인데 이 기본 크기를 원하는 값으로 바꿀 수 있다.
그래서 #pragma pack(1) 이라고 선언하면 기본 크기가 1이 된다.
그래서 TEST3의 결과를 보면.
위와같이 1+2+4+8 = 15 의 크기가 됨을 알 수 있다.
하지만 이같은 경우에는 컴퓨터가 기본 메모리를 읽을떄 32비트 OS 에서는 32비트, 64비트 OS 에서는 64비트 단위로 한번에
읽어오기 떄문에 한번 읽고 뒤로 되돌아 가야 하는 경우가 매번 생기기 떄문에 속도의 저하가 발생한다.
이런 문제를 해결하기 위해 #pragma pack() 전처리문을 선언이 끝난 마지막에 한번 더 사용해 주는데 파라미터로 아무 값도
주지않고 선언하면 기본크기로 재 설정된다.
위에서 두번째 예제에서 용량이 20바이트라고 생각했는데 24바이트가 나와서 의문을 가지고 좀 더 알아 본 결과
패딩이 단순이 32비트 프로그램이라고 4바이트로 패딩되는것이 아니었다.
패딩에도 기본 규칙이 있는데 이는 아래와 같다.
여기서 두번째 규칙에 의해 예제에 나온 모든 구조체는 8바이트로 패딩이 이루어 지고 있었다.
다시 설명하자면 TEST1 구조체의 메모리 설정은
[ [c] [s] [s] [-] ] [ [i] [i] [i] [i] ] [ [d] [d] [d] [d] ] [ [d] [d] [d] [d] ] 이런 구조가 아닌
[ [c] [s] [s] [-] [i] [i] [i] [i] ] [ [d] [d] [d] [d] [d] [d] [d] [d] ] 이런식으로 8바이트씩 들어간거 같다.
(좀더 찾아보다보니 패딩이 이루어질떄 만약 4바이트라면 css- 이런 형태가 아닌 c-ss 이런식으로 채워 넣는다는데
위예제처럼 세가지 데이터가 들어갈떈 어떻게 들어가게 되는지 궁금.. 얘도 조금 더 찾아보자 )
마찬가지로 TEST2 구조체는
[ [s] [s] [-] [-] [i] [i] [i] [i] ] [ [d] [d] [d] [d] [d] [d] [d] [d] ] [ [c] [-] [-] [-] [-] [-] [-] [-] ] 의 형태가 되어
총 24바이트의 크기를 갖는 구조체가 되는 것이다.
==============================================================================================================
패딩규칙에 대한 설명이 잘 나와있는 곳이 있어 링크를 건다.
본문내용
서론 :
지금 까지 나는 byte padding이 cpu 레지스터 사이즈를 따라 일괄적으로 적용 되는 줄 알고 있었다(http://nsjokt.springnote.com/pages/4716509). 하지만 오늘 exe와 dll 사이에서 일어난 문제를 해결하며 찾아본 자료에서 byte padding은 아래의 네 가지 규칙에 따라 구조체에 따라 다르게 적용 된다는 사실을 알았다. 예를 들어 설명 하자면 지금까지 페이지 사이즈가 4byte인 어플리케이션에서는 byte padding 사이즈를 따로 지정해 주지 않는 한 1 byte 짜리 멤버 변수를 가진 구조체나 4 byte 멤버 변수를 가진 구조체의 sizeof 결과가 모두 4 byte로 같을 것이라고 생각 했으나 실제로는 각각 1 byte, 4 byte로 다르게 align 되고 있었다.
위에서 언급한 네 가지 규칙을 살펴 보자면(일단 VC 기준, gcc는 다른 옵션이 있겠지) :
- Unless overridden with __declspec(align(#)), the alignment of a scalar structure member is the minimum of its size and the current packing.
- Unless overridden with __declspec(align(#)), the alignment of a structure is the maximum of the individual alignments of its member(s).
- A structure member is placed at an offset from the beginning of its parent structure which is the smallest multiple of its alignment greater than or equal to the offset of the end of the previous member.
- The size of a structure is the smallest multiple of its alignment larger greater than or equal the offset of the end of its last member.
출처 : http://msdn.microsoft.com/ko-kr/library/83ythb65(v=vs.100).aspx
나름 쉽게 풀어쓴다고 썼지만 나도 알아 먹기 힘드니 예제를 통해서 알아 보도록 하자
1. Unless overridden with __declspec(align(#)), the alignment of a scalar structure member is the minimum of its size and the current packing.
"__declspec(align(#))을 오버라이드 하지 않으면, 스칼라 구조체(long, bool과 같은 일반 변수로만 이루어진 구조체)의 멤버는 변수의 사이즈와 현재 지정된 byte padding align을 따른다."
이해를 돕기 위해 아래와 같이 두 가지 타입의 구조체를 만들어 보았다 :
{
bool b;
};
struct Size_4_Align
{
long l;
};
int main()
{
std::cout << "Size_1_Align:" << sizeof(Size_1_Align) << std::endl;
std::cout << "Size_4_Align:" << sizeof(Size_4_Align) << std::endl;
}
output :
Size_1_Align:1
Size_4_Align:4
bool 는 1 byte, long은 4 btye, 각 구조체의 사이즈는 각각의 멤버 변수 사이즈 대로 만들어진다.
2. Unless overridden with __declspec(align(#)), the alignment of a structure is the maximum of the individual alignments of its member(s).
"__declspec(align(#))을 오버라이드 하지 않으면, 구조체는 멤버 변수중 가장 사이즈가 큰 멤버 변수의 byte padding align을 따른다."
그럼 위의 Size_1_Align 구조체에는 2 byte 변수를, Size_4_Align 구조체에는 long(4byte) 보다 작은 사이즈의 멤버 변수를 추가해 보겠다. 가장 큰 사이즈 멤버 변수의 align을 따른다고 했으므로 Size_1_Align은 2 byte align 규칙을 따를 것이고, Size_4_Align 구조체는 4 byte align 규칙을 따를 것이다 :
struct Size_1_Align
{
bool b; // byte padding 규칙에 따라 요 부분은 1 byte가 더 채워 질 것입니다.
short s;
};
struct Size_4_Align
{
long l;
bool b;
short s;
};
int main()
{
std::cout << "Size_1_Align:" << sizeof(Size_1_Align) << std::endl;
std::cout << "Size_4_Align:" << sizeof(Size_4_Align) << std::endl;
}
output :
Size_1_Align:4
Size_4_Align:8
예상대로 각각 4 bypte 와 8 byte 가 나왔다.
3. A structure member is placed at an offset from the beginning of its parent structure which is the smallest multiple of its alignment greater than or equal to the offset of the end of the previous member.
"구조체 멤버는 부모 구조체의 byte padding align의 시작 offset에서 부터 이전 멤버 변수의 끝 offset 뒤에 자리 잡는다."
여기서 부모 구조체의 byte padding align에 대해서 잠깐 설명하자면, 부모가 4 byte padding 규칙을 가지고 실제 7 바이트 멤버만 가지고 있는 경우 7 byte 보다 큰 4 의 배수 byte offset을 시작 점으로 잡는다는 의미다. 예를 들어 :
struct Size_4_Align
{
long l;
short s; // bool b 와 위치가 바뀌었음.
bool b; // short s 와 위치가 바뀌었음.
};
struct Derived_4_Align : public Size_4_Align
{
char c;
};
int main()
{
std::cout << "Size_4_Align:" << sizeof(Size_4_Align) << std::endl;
std::cout << "Derived_4_Align:" << sizeof(Derived_4_Align) << std::endl;
}
output :
Size_4_Align:8
Derived_4_Align:12
Derived_4_Align 구조체는 Size_4_Align을 상속 받아 1 byte(char) 짜리 변수를 추가 했다. 생각해 보면 Size_4_Align의 경우 멤버 변수 나열이 4, 2, 1 byte로 나열 되었으므로 뒤에 1 바이트가 추가 되면 8 byte로 align이 될 수도 있지 않을까 생각했지만 에누리 없이 기존 8 byte에 더하여 (그리고 최대 사이즈 멤버 변수가 4 byte이므로) 12 byte 짜리 구조체가 되었다.
4. The size of a structure is the smallest multiple of its alignment larger greater than or equal the offset of the end of its last member.
"구조체의 사이즈는 가장 마지막 멤버 변수의 offset 을 기준으로 align 되는 byte의 최소 배수이며 offset과 같거나 큰 사이즈를 가진다."
말이 조금 달라지긴 했지만 위 3번에서 설명 했던 byte padding align과 같은 의미다. 결국 구조체의 사이즈는 byte padding을 통해 가장 페이징 하기 좋은 사이즈로 결정 된다는 의미다.
{
long long ll;
bool b;
};
int main()
{
std::cout << "Size_8_Align:" << sizeof(Size_8_Align) << std::endl;
}
output :
Size_8_Align:16
결국 가장 큰 멤버 변수 사이즈(8 byte)로 align 되는 구조체는 1 byte가 더 추가 되더라도 8 byte 가 증가 하며 (align 사이즈의 배수) 실제 구조체 사이즈인 9 byte 보다는 크다는 것이다.
결론 :
오늘의 핵심은 구조체의 byte padding은 모든 구조체에 일괄적용 되는 것이 아니라 규칙에 따라 각각의 최적의 align 사이즈를 가지고 있다는 것이다.
'Programming > C, C++, MFC' 카테고리의 다른 글
rand() 는 랜덤이 아니다?? 이제는 랜덤을 바꿔야 할 때! (0) | 2014.11.21 |
---|---|
fatal error LNK1123: COFF로 변환하는 동안 오류가 발생했습니다. (0) | 2014.07.29 |
함수의 파라미터로 포인터를 사용 (0) | 2014.07.02 |
STL List (0) | 2014.06.09 |
[Function][MFC] 폴더 내 모든파일 삭제하기 (0) | 2014.03.25 |