출처 : 벌나래 ( HOMEPAGE )
사유 : 귀중한 데이터가 날라갈까봐 블러그에 옮겨 둔다. 자세한 설명이 아주 일품이다
원문 : 링크

컴파일러가 특정 머신에서 기계어 코드로 변환되어 데이터를 저장하고 가져오는데는 보통 word 단위(4 byte)로 입출력하는 경우가 많다. 이것은 시스템의 효율성을 가져오기 위해서 단위 출력/입력을 하는 것이다.

원래 이렇게 되는 것은 CPU의 특성때문이다. CPU 에서 가장 비용이 많이 들어가는 것은 메모리에 접근하는 작업인데 이 작업이 잦으면 별로 효율적이지 못하게 된다.

따라서 CPU는 (보통 32bit 머신이라면) 주변에 4 byte(32bit)의 데이터 공간을 같이 가져온다. 따라서 1 cycle당 32 bit 단위로 한번에 가져오게 되는 것이다. (항상 32bit는 아니다. 이는 절대적이 아니다.)
: 2008/03/24 추가
배틀넷 개발을 위한 Network Game Server Programming 책에선 컴파일러가 기본적으로 8바이트 정렬을 한다고 말한다. 개인적은 추측으로 CPU가 64bit형이기 때문이지 않을까 한다.(요즘 CPU는 64bit형에 32bit 호환형이다) 컴파일러의 컴파일 환경에 따라, 결정 되므로, 기본적으로 8바이트 정렬은 틀렸다.

이를 컴파일러에서 이용하여 프로그램의 효율을 좋게 하는데, 일부러 데이터를 프로세서가 한번에 접근하는 공간에 단위배수에 맞게 정렬(alignment)하여 데이터를 넣어두면 한번에 딸려오는 뒷 데이터가 있으므로 실제로 메모리에 접근하는 횟수를 줄이고, 효율을 좋게 할 수 있는 것이다.

따라서 컴파일러는 CPU의 이런 설계구조를 통해서 메모리 접근 효율을 좋게 하는 것이다.)
그런데 이런 특성을 컴파일러도 이용하여 속도향상을 가져오게 되는데,  이때 발생하는 문제가 padding이란 것을 포함하여 원래 의도한 바와 다르게 나타나는 구조체의 데이터 크기다.

아래 구조체를 보자.
코드:

typedef struct _MY_ST_A {
char str[10];
char cnt[4];
} MY_ST_A;

typedef struct _MY_ST_B {
char str[10];
int cnt;
} MY_ST_B;

typedef struct _MY_ST_C {
char str[9];
short cnt;
} MY_ST_C;

typedef struct _MY_ST_D {
char str[10];
double cnt;
} MY_ST_D;

위의 구조체들의 크기(sizeof) 는 어떻게 될까?  사용되는 구조체변수의 주소번지까지 찍어주었다.

코드:

(platform: 32bit machine / solaris)
address / sizeof(MY_ST_A) : 0xffbef3d8 / 14
address / sizeof(MY_ST_B) : 0xffbef3c8 / 16
address / sizeof(MY_ST_C) : 0xffbef3b8 / 12
address / sizeof(MY_ST_D) : 0xffbef3a0 / 24


맨 처음 구조체 녀석(MY_ST_A)은 당연히 char 형 배열 10 byte와 4 byte 가 있으니 sizeof()값이 14 byte 인게 맞다. 근데 두번째 구조체는 보면 16 byte 이다.

이것은 구조체의 두번째 요소가 int 형 (4btye) 이기 때문에 4 byte 기준으로 정렬되므로4의 배수가 된다. 따라서 모든 요소의 시작은 4 의 배수에서 시작되어야만 한다. 세번째는 short 형(2byte)이므로 2 byte로 정렬되고, double은 8 byte로 정렬되는 것이다.

그러면 그림으로 그려서 보자. MY_ST_A 는 원래 char 형으로 다닥다닥 붙어있으니 머릿속에서 연상도 쉽게 된다.

그래서 두번째와 네번째만 함 그려보자.(편의상 시작 주소는 0x0000 으로 하자.)

코드:

* MY_ST_B 구조체
+----------------+ 0x0000
| char str[0] |
+----------------+ 0x0001
| char str[1] |
+----------------+ 0x0002
| .............. |
| .............. | 중간생략(str[2] - str[8] 까지)
| .............. |
+----------------+ 0x0009
| char str[9] |
+----------------+ 0x000a : 아래 2 byte 는 padding 공간이다.(4 의 배수중 가장 작은 12으로 맞추기 위해서)
| padding |
+----------------+ 0x000b
| padding |
+----------------+ 0x000c : alignment size가 4byte (int형)이므로 4의 배수인 12 byte 부터 int cnt가 시작된다.
| |
+--- ----+ 0x000d
| |
+--- int cnt ----+ 0x000e
| |
+--- ----+ 0x000f
| |
+----------------+ 0x0010 => Therefore, sizeof() 값이 16 byte 가 나온다.
| .............. |


코드:

* MY_ST_D 구조체
+----------------+ 0x0000
| char str[0] |
+----------------+ 0x0001
| char str[1] |
+----------------+ 0x0002
| .............. |
| .............. | 중간생략(str[2] - str[8] 까지)
| .............. |
+----------------+ 0x0009
| char str[9] |
+----------------+ 0x000a : 아래 6 byte 는 padding 공간이다.(8 의 배수중 가장 작은 16으로 맞추기 위해서)
| padding |
+----------------+ 0x000b
| padding |
+----------------+ 0x000c
| padding |
+----------------+ 0x000d
| padding |
+----------------+ 0x000e
| padding |
+----------------+ 0x000f
| padding |
+----------------+ 0x0010 : alignment size가 8 byte (double형)이므로 8의 배수인 16 byte 부터 double cnt가 시작된다.
| |
+--- ----+ 0x0011
| |
+--- double ----+ 0x0012
| |
+--- cnt ----+ 0x0013
| |
+--- ----+ 0x0014
| |
+--- ----+ 0x0015
| |
+--- ----+ 0x0016
| |
+--- ----+ 0x0017
| |
+----------------+ 0x0018 => Therefore, sizeof() 값이 24 byte 가 나온다.
| .............. |

자 MY_ST_B와 MY_ST_D 와의 차이점이 쉽지 않은가?
가장 큰 alignment 값에 맞추어지게 된다는 것을 보면 쉽게 알 수 있다.

따라서 char 와 int , double 을 섞어서 만들면 가장 큰 double 형에 맞춰져서 구조체 크기가 맞춰진다.  이런 padding 의 원리를 제대로 알고 제대로 구조체를 잡아야만 네트워크를 통해서 데이터를 주고받을때  데이터의 시작 위치가 제대로 맞춰진다. (이런 오류를 피하기 위해서 중간에 아무런 데이터도 없는 char 형을 끼워넣기도 한다.)

코드:

typedef struct _MY_ST_D {
char str[10];
double cnt;
} MY_ST_D;

typedef struct _MY_ST_E {
char str[10];
char filler[6];
double cnt;
} MY_ST_E;

위의 두개의 구조체는 얼핏보기엔 MY_ST_E가 더 커보이지만, 실제 메모리상의 위치하는 크기는 동일하다.

이유는 위에서 입이 닳도록 설명한 padding 위치에 데이터가 들어갔기 때문이다. 그런데 가끔은 이런 padding을 무시하고 싶은 경우도 있을것이다. 따라서 padding을 하지말고 강제로 끼워맞추는 것이 바로 packed 라는 것이다.  packed를 이용하면 padding 공간을 무시하고 강제로 데이터를 끼워넣는다.  각 컴파일러마다 다르지만, gcc 에서는 아래와 같이 사용한다.

코드:

typedef struct _MY_ST_D {
char str[10];
double cnt;
} __attribute__ ((packed)) MY_ST_D;

혹은
struct _MY_ST_D {
char str[10];
double cnt;
} __attribute__ ((packed));

위와 같이 하면 데이터 크기는 18 byte 가 된다. 당연히 padding 공간이었던 6 byte 가 빠져나가기 때문이다.

packed 를 하면 데이터가 따닥따닥 붙어서 alignment에 신경쓰지 않아도 되지만,  이는 성능상으로는 안좋기 때문에 별로 쓰라고 권하고 싶지 않다.
따라서 packed 는 특수한 경우(임베디드나 커널 프로그래밍)가 아니면 거의 사용되지 않는다.

* packing 하는 방법
1) source code 내의 특정 struct 에 __attribute__ ((packed)) 지시자를 사용한다.
2) gcc 컴파일시에 --pack-struct 를 써도 같다.
이외에 gcc 에서는 __attribute__  지시자를 이용해서 alignment 배수를 바꿀 수도 있다. 실제로 아래처럼 char 이지만 4 byte 단위로 데이터를 배열할 수도 있다. 이렇게 하면 위에서는 14 byte 가 나왔지만, 16 byte (4의 배수)가 나온다. 참고로 gcc 에서는 __attribute__ 를 이용해서 많은 것을 바꿀 수 있다. 참고로 아래 GCC 매뉴얼 URL도 있으니 심심하면 보도록...

코드:

typedef struct _MY_ST_A {
char str[10] __attribute__((aligned(4)));
char cnt[4];
} MY_ST_A;


'연구실 > 파편화된 기록들' 카테고리의 다른 글

경험  (0) 2008.05.28
프로그래머로써의 마음자세  (0) 2008.05.24
윈도우 XP 서비스팩 3 공식 업데이트 되다.  (0) 2008.05.24
용어에 대한 선택  (0) 2008.05.18
윈도우 창과 콘솔창 두개 띄우기  (0) 2008.05.16
프로그래머에게 필요한것들  (0) 2008.05.12
내가 가지고 있는 책  (0) 2008.05.11
BOOL vs bool  (3) 2008.05.11
C++0x : 새로운 C++의 문법  (0) 2008.05.10
STL #  (2) 2008.05.10
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 라이프코리아트위터 공유하기
  • shared
  • 카카오스토리 공유하기