구조체 패딩 비트에 대해서. struct padding bit

by Blogger 하얀쿠아
2011. 8. 20. 21:13 소프트웨어 Note/C++ MFC

아래와 같은 구조체를 선언했다고 하자.


char 1바이트이고 int 4바이트인 시스템에서 위의 구조체를 선언하고 sizeof()로 구조체의 사이즈를 찍어보면 얼마가 나올까? 생각대로라면 5바이트가 나와야 한다. 1 + 4 = 5 이니까..

 

그런데 대부분의 컴파일러에서 실제로는 8바이트가 나온다. 이유는 패딩 비트가 추가되어서 그렇다. 몇몇 컴파일러는 구조체의 필드를 메모리에 위치 시킬 때 중간에 빈 공간 없이 쭉 이어서 할당하는 경우도 있지만, 대부분의 컴파일러는 성능향상을 위해 CPU가 접근하기 쉬운 위치에 필드를 배치한다. 그러다 보니 중간에 빈 공간이 들어가게 되는 것이다. 이 빈 공간이 바로 패딩 비트이다.

 

이에 대해서 좀 더 자세히 알아보자.

 

32비트 CPU는 메모리에서 값을 읽어 올 때 한번에 4바이트(32비트), 64비트 CPU는 한번에 8바이트(64비트)를 읽어온다.

 

32비트 CPU를 가진 시스템에서 CPU가 메모리상에 정의된 위 구조체의 a 멤버로 접근하려면 어떻게 해야 할까? 간단하다. 구조체 test의 시작번지에서 32비트를 읽어와서 그 중 맨 앞 8비트만 사용하면 된다.

 

그럼 이제 그 다음 멤버인 b에 접근하려면 어떻게 해야 할까? 이건 좀 복잡해진다. 구조체 test의 시작번지에서 32비트를 읽어와도 멤버 b의 비트에 모두 접근 할 수 없다. 그래서 두 번 메모리를 읽어서( 64비트), 첫 번째 읽은 값에서 뒤의 24비트와 두 번째 읽은 값에서 앞의 8비트를 합쳐서 멤버 b의 값을 구할 수 있다. 이렇게 하면 한번에 할 일을 두 번에 걸쳐서 하는 것이기 때문에 당연히 성능저하가 발생한다.

 

그래서 대부분의 컴파일러는 CPU가 접근하기 쉬운 메모리 위치에 필드를 배치시키기 때문에 아래와 같이 패딩 비트가 자동으로 들어가게 된다.

 

char a: 1byte

padding bit: 3bytes

int b: 4bytes

 = 8byte

위와 같이 패딩 비트가 들어가서 총 8바이트가 되면 CPU가 각 멤버에 접근할 때 한번씩만 메모리를 읽으면 각 멤버의 값을 구할 수 있다. 쓸모 없는 메모리를 3바이트나 낭비하는 꼴이 되어버리지만 CPU가 각 멤버에 접근 할 때 한번씩만 메모리를 읽으면 되기 때문에 성능저하가 발생하지 않는다.

 

하지만 패딩 비트에도 문제점이 발생한다.

구조체를 사용할 때 대부분의 경우에는 위와 같은 복잡한 패딩 비트에 대해 신경 쓸 필요는 없다. 가끔 구조체의 전체 사이즈가 프로그래머가 생각 했던 것과 다르게 나오는 게 문제가 되는 경우도 있겠지만..

 

그런데 네트워크를 통해서 구조체 자체를 전송하려고 하면 패팅 비트가 굉장히 중요한 변수가 된다. 왜냐하면 구조체가 메모리에 정의되는 형태는 OS와 컴파일러에 따라 달라지기 때문이다. 동일한 구조체를 서로 다르게 메모리에 정의하고 있는 시스템끼리 메모리에 있는 구조체 내용을 그대로 주고 받는다면 구조체의 각 멤버는 서로 다른 값을 가지게 된다.

 

패딩 비트는 삽입되는 위치가 컴파일러에 따라 달라진다. 또한 32비트와 64비트 시스템은 동일한 구조체에 대해서 삽입하는 패딩 비트의 수가 각각 다르다.

 

그럼 시스템 A(32비트)가 시스템 B(64비트)에게 아래의 구조체를 네트워크로 전송하고, 시스템 B는 받은 패킷을 아래 구조체로 캐스팅해서 그대로 사용하는 경우 어떤 문제가 생길까?

 

* 참고로 char = 1byte, long long = 8bytes

struct test_s

{

  char a;

  long long b;

} test;

 

32비트 시스템에서는 위 구조체를 사용할 때 멤버 a 뒤에 3바이트의 패딩 비트를 넣어서 구조체 사이즈가 12바이트가 된다. 반면에 64비트 시스템에서는 7바이트의 패딩 비트를 넣어서 구조체 사이즈가 16바이트가 된다.

 

따라서 구조체의 시작위치에 있는 첫 번째 멤버 a는 값이 변하지 않지만, 그 다음 멤버들은 값이 다 바뀌게 된다.

 

그럼 어떻게 하면 구조체를 네트워크로 안전하게 전송 할 수 있을까? 여기엔 두 가지 방법이 있다.

 

첫째, #pragma 또는 #packed 키워드를 사용해서 컴파일러가 패딩 비트를 사용하지 않도록 하는 방법이 있다. 하지만 이 방법은 C 표준이 아닌 관계로 이식성이 없다.

 

#pragma pack(1)

struct test_s

{

  char a;

  int c;

} test;

#pragma pack(8)

구조체 선언할 때 1 byte 단위로 pack 시킨다고 지정해 주고 선언이 끝난 후에 적절히 4 byte 8 byte (원래대로) 확장(원복) 시켜 주는 방법이다.

 

둘째, 프로그래머가 패딩 비트를 수동으로 관리해주면 된다. , 아래와 같이 구조체를 만들 때 dummy 값을 패딩비트로 넣으면 된다.

 

struct test_s

{

  char a;

  char b[3];

  int c;

} test;

 

위에서 멤버 b가 패딩 비트이다. 위 구조체는 4의 배수인 8바이트의 사이즈를 가진다. 그러나 몇 바이트를 기준으로 정렬할지는 순전히 OS와 컴파일러에 따라 달라진다. 따라서 네트워크로 구조체를 직접 보내는 방법은 올바른 방법이 아닌 것으로 생각된다.

 

그럼 하나 더 생각을 해보자. 무조건 4의 배수로만 구조체의 크기를 맞춰주면 문제가 해결이 될까?

int(4바이트)를 기준으로 맞추면 대부분의 경우 정확하다. long long 변수가 있는 경우는 8 바이트를 기준으로 맞춘다. 그러나 OS와 컴파일러에 따라서 약간의 특성을 타기 때문에 항상 보장된다고는 볼 수 없다.

 

struct test_s

{

  char a;

  char b[7];

  long long c;

} test;

 

하지만 아직 바이트오더 문제가 남아있다. 이는 아래 문서를 참조하면 된다.

http://superkkt.com/138

이 댓글을 비밀 댓글로