Egloos | Log-in
F/OSS study
F/OSS study
[C] 구조체 정렬 제한 및 패딩
gcc: 4.5.0
arch: x86_64


정렬 제한이란 데이터가 메모리에 저장될 때 해당 메모리의 주소에 대한 제약 사항을 말하는 것이다.
구체적으로는 데이터가 저장될 위치가 특정한 단위로 정렬(배치?) 되어 있어야 한다는 것을 뜻한다.

예를 들어 CPU 아키텍처에서 메모리 접근 시에 32비트 단위로 정렬된 주소에만 접근할 수 있다고 하자.


위의 데이터 A는 주소값이 4의 배수 형태이므로 정렬되어 있지만, B의 경우는 그렇지 않다.
B의 경우 해당 데이터에 접근 시 실행 환경에 따라 다음과 같은 여러 가지 결과를 일으킬 수 있다.
  • 비정상적인 메모리 접근을 인식하여 OS가 프로그램을 종료시킨다.
  • CPU 혹은 컴파일러가 B가 속한 두 메모리 영역에 접근한 뒤 적절한 비트 연산을 거쳐 원하는 값을 만들어준다.
  • 주소를 적당히 4의 배수로 변경하여 메모리에 접근한다.
  • 별 문제없이 실행된다.

어떤 경우이든 정렬되지 않은 메모리에 대한 접근은 성능에 도움이 되지 않는다.
최악(?)의 경우 B가 걸쳐있는 두 메모리 워드가 각자 다른 캐시 라인에 속할 수도 있음을 생각해 보자.
따라서 컴파일러는 컴파일 시에 이러한 정렬 제한을 고려하여 데이터를 메모리 상에 배치한다.
(물론 최종적으로는 링커가 이러한 작업을 수행한다.)

C 언어의 기본 데이터 타입은 모두 자신의 크기와 동일한 정렬 제한을 가진다.
gcc에서 이러한 정보는 sizeof와 __alignof__를 통해서 얻을 수 있다.

/* align-basic.c */

#include <stdio.h>

char c;
short s;
int i;
long l;
float f;
double d;

int main(void)
{
#define print_align(type, var)                        \
  printf(#type "\t%zd\t%zd\t%p\n", sizeof(type), __alignof__(type), &var)

  printf("type\tsize\talign\taddress\n");
  print_align(char, c);
  print_align(short, s);
  print_align(int, i);
  print_align(long, l);
  print_align(float, f);
  print_align(double, d);

  return 0;
}

위의 예제 프로그램을 빌드한 후 실행해보면 다음과 같은 결과를 얻을 수 있다.

$ gcc align-basic.c
$ ./a.out
type     size    align    address
char     1       1        0x40209a
short    2       2        0x402098
int      4       4        0x402094
long     8       8        0x402088
float    4       4        0x402090
double   8       8        0x402080

데이터 타입의 크기와 정렬 제한은 모두 동일한 크기를 가지며
실제 변수가 생성된 주소도 이러한 정렬 제한에 맞추어 졌음을 볼 수 있다.
좀 더 자세히 살펴보면 동일한 크기/정렬 제한을 가지는 데이터끼리 묶어서 배치하였으며
공간을 효율적으로 사용하기 위해 큰 크기/정렬 제한을 가지는 데이터부터 배치한 것도 볼 수 있다.

정렬 제한은 포인터 변환 시에도 반드시 고려해야 할 부분인데
cast 연산을 통해 강제로 변환한 주소값이 변환된 포인터가 가리키는 데이터 타입의
정렬 제한을 어길 수 있기 때문이다. 예를 들어 위의 예제에서
변수 i의 주소를 long * 타입으로 변환한다면 long 타입이 요구하는 8 바이트의 정렬 제한을
만족시키지 못하므로 해당 포인터를 통해 메모리에 접근 시 위에서 언급한 결과 중의 하나를 얻게 될 것이다.

이제 본격적으로 구조체에 대해서 살펴보기로 하자.
구조체는 여러 데이터 타입을 모아둔 집합체의 개념이므로 구조체를 이루는 각 멤버에 대해서도
위에서 언급한 정렬 제한을 모두 만족시켜야 한다. 또한 구조체는 위의 예제에서와 같이
메모리를 효율적으로 사용하기 위해 구조체 멤버의 순서를 임의로 조정할 수 없으며
반드시 구조체 선언 시에 명시된 순서대로 멤버를 메모리 상에 배치해야 한다.
따라서 이를 조정하기 위해 멤버 사이에 사용하지 않는 패딩 공간을 할당할 수 있다!!

구체적인 예를 통해 살펴보기로 하자.

/* align-struct.c */

#include <stdio.h>

struct sc {
  char c;
} sc;

struct sl {
  long l;
} sl;

struct scl {
  char c;
  long l;
} scl;

struct slc {
  long l;
  char c;
} slc;

int main(void)
{
#define print_align(type, var)                        \
  printf(#type "\t%zd\t%zd\t%p\n", sizeof(type), __alignof__(type), &var)

  printf("type\t\tsize\talign\taddress\n");
  print_align(struct sc, sc);
  print_align(struct sl, sl);
  print_align(struct scl, scl);
  print_align(struct slc, slc);

  return 0;
}

실행 결과를 살펴보기 전에 먼저 결과가 어떻게 나오게될 지 생각해 보자.
멤버로 오직 하나의 데이터 만을 가지고 있는 sc와 sl의 경우는
단순히 해당 멤버의 크기와 정렬 제한을 그대로 갖게될 것이다.
하지만 scl과 slc와 같이 서로 다른 데이터 타입으로 이루어진 경우라면 어떨까?

위의 예제를 실행해 보면 다음과 같은 결과를 얻을 수 있다.

$ gcc align-struct.c
$ ./a.out
type          size    align    address
struct sc     1       1        0x4020a8
struct sl     8       8        0x4020a0
struct scl    16      8        0x402080
struct slc    16      8        0x402090

앞서 말한대로 sc와 sl의 경우에는 특별한 사항이 없다.

scl의 경우에는 우선 정렬 제한이 8이라고 나오는데 이는 멤버 중의 가장 큰 정렬 제한을
가지는 long 타입의 정렬 제한을 그대로 물려받은 것이다.
또한 크기는 9가 아닌 16으로 나오는데 이는 8 바이트 단위로 정렬된 위치에 첫 멤버인
char 타입의 데이터가 저장되고 그 이후에 long 타입의 데이터가 다시 8바이트 단위로
정렬된 위치에 저장되기 때문이다. 즉 1~7번 바이트는 사용되지 않고 단순히 long 타입의
멤버의 정렬 제한을 보장하기 위한 용도로 채워진 패딩 바이트인 것이다.

구조체 내의 각 멤버의 위치는 offsetof 매크로를 통해 조사할 수 있는데
위의 경우 offsetof(struct scl, l)의 값은 8이 될 것이다.

slc의 경우에는 마찬가지로 정렬 제한이 8이고 크기가 16으로 나오는데
정렬 제한이야 그렇다 하겠지만 크기는 왜 9가 아닌 16으로 나오는 것일까?
만약 크기가 9이더라도 long 타입과 char 타입의 정렬 제한을 이미 모두 만족시키고 있는데
왜 굳이 구조체 뒤쪽에 불필요한 패딩 바이트를 추가하여 크기를 크게 만들었을까?

그 해답은 바로 배열 때문이다.
알고 있듯이 배열은 구조체와 같은 집합체이지만 동일한 데이터 타입으로 이루어진 것이며
중요한 사항은 배열을 이루는 각 원소들은 메모리 상에서 연속된 영역에 존재해야 하며
각 원소들 자체에 대해서도 정렬 제한을 만족시켜야 한다는 점이다.
만일 slc의 크기가 9가 된다면 배열의 다른 요소들은 정렬 제한을 만족하지 못할 것이므로
slc 내에 패딩을 포함시켜 slc의 크기 자체를 정렬 제한의 배수가 되도록 맞춘 것이다.

이렇게 구조체 내의 패딩 바이트가 추가되는 규칙을 알고 있다면
구조체 선언 시 각 멤버들의 위치를 잘 선택하여 효율적인 메모리 배치를 이루도록 할 수 있을 것이다.

하지만 상황에 따라 이러한 구조체의 크기/정렬 제한을 조정해야 하는 경우가 있을 수 있는데
(일반적으로는 그리 추천할 만 한 방법은 아닐 것이다)
이 때는 gcc에서 확장 기능으로 제공하는 aligned 혹은 packed 속성을 이용하면 된다.

역시 예제를 통해 살펴보기로 하자.

/* align-adjust.c */

#include <stdio.h>

struct asc {
  char c __attribute__((aligned(8)));
} sc;

struct __attribute__((aligned)) asl {
  long l;
} sl;

struct __attribute__((packed)) pscl {
  char c;
  long l;
} scl;

#pragma pack(push, 2)
struct pslc {
  long l;
  char c;
} slc;
#pragma pack(pop)

int main(void)
{
#define print_align(type, var)                        \
  printf(#type "\t%zd\t%zd\t%p\n", sizeof(type), __alignof__(type), &var)

  printf("type\t\tsize\talign\taddress\n");
  print_align(struct asc, sc);
  print_align(struct asl, sl);
  print_align(struct pscl, scl);
  print_align(struct pslc, slc);

  return 0;
}

앞서 말한대로 구조체의 정렬 제한은 멤버 중 가장 큰 값을 물려받는다.
따라서 구조체의 정렬 제한을 변경하는 대신 멤버의 정렬 제한을 변경한 경우에도
구조체에 영향을 미치게 된다. aligned 속성에는 2의 제곱수에 해당하는 숫자 만을
인자로 사용할 수 있으며 기본 정렬 제한보다 큰 값을 적용하는 경우에만 의미가 있다.
인자를 생략한 경우 실행 환경에 가장 적합한 정렬 제한을 가지도록 컴파일러가 결정한다.
반대로 packed 속성의 경우에는 정렬 제한을 가장 작은 수인 1로 줄이게 되며
1이 아닌 값을 적용하기 위해서는 #pragma를 이용할 수 있다.

위의 예제를 실행하면 다음과 같은 결과를 얻을 수 있다.

$ gcc align-adjust.c
$ ./a.out
type          size    align    address
struct asc    8       8        0x4020a8
struct asl    16      16       0x402080
struct pscl   9       1        0x40209a
struct pslc   10      2        0x402090

구조체에 비트 필드가 추가되는 경우 보다 복잡/미묘한 상황이 발생할 수 있는데
이에 대해서는 "더 이상의 자세한 설명은 생략한다".. ;;


=== 참고 문헌 ===

by namhyung | 2010/10/03 19:06 | General | 트랙백(2) | 덧글(6)
트랙백 주소 : http://studyfoss.egloos.com/tb/5409933
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Tracked from 김재호의 디지털보단 아.. at 2011/12/25 17:57

제목 : 구조체의 패킹에 대한 이야기
MSDN에 따르면 구조체의 디폴트 패킹 값은 8이다. 간혹 32비트 운영체제에서는 4바이트이고 64비트 운영체제에서는 8바이트라고 주장하는 사람들도 있는데, 디폴트 패킹 크기는 컴파일러가 결정하지 운영체제가 결정하는 것이 아니다. MSVC에서 디폴트 패킹을 8바이트로 정한 이유는(32비트 운영체제에서 조차) 기본 타입 중 가장 큰 타입이 8바이트이기 때문이다. 만약 이후에 16바이트 포인터나 INT128 같은 타입을 기본 타입으로써 사용하는 날......more

Tracked from at 2014/03/11 00:25

제목 : garcinia cambogia reviews
line3...more

Commented by DMW at 2010/10/10 17:18
좋은 글이 무지 많네요. 잘 읽고 RSS도 퍼갔습니다.

방명록을 찾을 수 없어서 이쪽에다 리플을 적었습니다.
Commented by namhyung at 2010/10/11 17:33
댓글 남겨주셔서 감사합니다.
조금이나마 도움이 되셨길 바랍니다.. ^^
Commented by SY Kim at 2011/01/26 05:54
이런 문제의 패딩을 없애기 위해서 의미없는 filler를 넣는 경우도 많죠.

예전에 프로젝트에서 이전 담당자가 패딩을 고려하지 않은 구조체 설계를 해놓았지요. 당시에는 C언어로 시스템이 되어있어서 별 문제가 없었지만 몇 년 후 자바 시스템이 추가되어 네트워크 데이터를 교환할 일이 생겼었죠. 그런데 자바 애플리케이션 개발자들이 패딩을 몰라서 난리가 난적이 있었던 적이 있었지요. (자바 개발자의 생각에는 왜 데이터의 크기가 달라지는지 이해할 수 없었나 봅니다.)

결국 원래 시스템의 구조체에 전부 filler를 넣었는데 라이브러리 수정하는 부분이라 큰 문제가 없는 작업인데도 윗선에서 confirm이 안나와서 엄청 딜레이 되었던 아름다운(?) 추억이 생각납니다.
Commented by namhyung at 2011/01/27 10:17
네.. 아름다운 추억을 공유해 주셔서 감사합니다.

아무래도 이런 문제는 서로 다른 환경에서 통신해야 하는 네트워크 프로그래밍에서 많이 발생하겠지요.. 위와 같은 문제로 인해 구조체 정렬 자체는 물론 향후 확장성을 위해 일부 영역을 안쓰는 채로 남겨두기도(reserve) 하는 경우가 많은 것으로 알고있습니다.
Commented by fivestar at 2011/11/17 11:46
예전에 펀더멘탈 보면서 본 내용인데 시간이 지나니 가물가물해졌었는데
정리잘해주셔서 제 머리도 정리가 잘 되었네요

감사합니다.
Commented by namhyung at 2011/12/04 14:33
답글 남겨주셔서 감사합니다.
조금이나마 도움이 되셨다니 다행입니다. ^^

:         :

:

비공개 덧글

◀ 이전 페이지 다음 페이지 ▶

카테고리
General
Application
System
Kernel
Book
Tips
태그
emacs CAaQA3 C gcc build CARM vcs linux block-layer documentation computer-architecture blktrace memory binutils bash elf awk algorithm scm git compiler patch SMP kernel x86 glibc script synchronization perf sed
전체보기
이글루 파인더

최근 등록된 덧글
bagsu infonya sobat https:..
by siherbak at 04/19
Terima kasih atas infonya..
by siherbak at 04/18
terima ksaih tas infonya ba..
by siherbak at 04/18
최근 등록된 트랙백
Tod's Ferrari Homme
by Tods Pas Cher,Kodak did ..
Mocassin Femme
by Mocassins Homme, I got so..
natural garcinia cambogia
by
rss

skin by jiinny


X