바이트 패딩이란?
컴파일러는 성능향상을 위해 CPU가 접근하기 쉬운 위치에 필드를 배치한다. 그러다보니 중간에 빈 공간이 들어가게 되는데 이를 바이트 패딩이라고 한다.
예시
typedef struct st
{
char c;
int i;
};
위의 구조체를 sizeof() 연산을 취한다면 어떤 결과가 나올까?
일반적으로 생각하면 5바이트라고 생각할 것이다.
하지만, 직접 sizeof() 연산을 해 보면 8바이트라는 값이 나온다.
이 때 추가된 3바이트를 바이트 패딩 값인데 어떻게 해서 3바이트가 추가된 것인지 알아보자.
32bit CPU를 사용한다고 가정하고 설명하자면, CPU가 메모리에서 값을 읽어올 때 한번에 4바이트를
읽어올 수 있다.
패딩 값을 고려하지 않고 위의 구조체의 필드값을 읽어오는 과정을 보면, 먼저 'c' 같은 경우에는 첫 주소로부터 4바이트를 읽어온 후 맨 앞의 1바이트만 사용하면 된다. 딱히 문제가 될 것이 없다.
하지만, 'i' 값을 읽어올 때 먼저 앞의 4바이트에서 i 값을 읽어오는데 i 값 전부를 읽어올 수 없기 때문에
그다음 4바이트까지 읽어서 i 값을 읽어오게 된다. 32bit CPU에서 한 번에 4바이트를 읽어올 수 있고
한 번에 int 형 변수를 읽어올 수 있기 때문에 이렇게 'i'라는 필드 값을 읽는데 메모리에 2번 접근한다는 것은 엄연히 낭비이다.
하지만, 패딩 값이 구조체에 들어가게 된다면 각 필드 값을 단 한번의 메모리를 읽는 과정으로 접근할 수 있게된다.
typedef struct st
{
char c;
// padding bit : 3byte
int i;
};
이렇게 되면 메모리를 3byte나 낭비하게 되지만, CPU가 필드 값에 접근할 때 한 번씩만 메모리를 읽으면 되기 때문에
성능 저하를 막을수 있게 된다.
위의 과정을 이해하기 쉽게 그림으로 보자면
먼저 'c'를 읽어올 때는 시작 주소로부터 4byte를 읽은 후 맨 앞의 1byte만을 이용하게 된다.
'i'를 읽는 과정에서도 먼저 시작 주소로부터 4byte 떨어진 주소까지 읽어오는데 i 전체를 읽어오지 못하기 때문에
한 번의 작업을 더 필요로 하게 된다.
CPU는 'i'라는 필드를 읽기 위해서 쓸데없이 2번의 처리 작업을 하게 되는 것이다.
따라서, 위의 구조체를 읽기 위해서 총 세 번의 처리 작업을 하게 된다.
이제 패딩 값을 넣고 살펴보자.
먼저 'c'를 읽는 과정은 위와 동일하다.
'i'를 읽는과정에서 앞의 4바이트에 접근할 필요가 없기 때문에 뒤의 4byte에만 접근을 하게된다.
따라서, 구조체 내의 모든 필드를 읽는데 2번의 과정만 거치면 되는 것이다.
사실 구조체에 패딩 값이 얼마가 들어가던 그렇게 중요하지는 않다.
다만! 네트워크를 통해서 구조체를 전송할 때는 굉장히 중요한 이슈가 되는데, 운영체제와 컴파일러에 따라서 구조체의 메모리가정의되는 형태가 달라지기 때문이다.
위에서 살펴본 32bit 머신의 패딩 값과 64bit 머신의 패딩값은 차이가 있는 점부터 시작해서
동일한 구조체가 서로 다르게 메모리에 정의되는 시스템 끼리의 패킷 송수신과정은 문제를 야기한다.
이러한 문제점을 해결하기 위해 #pargma pack() 이나 패딩 비트를 직접 넣어주는 행위는 동일한 플랫폼에서만
사용이 가능하다는 점을 보아 '직렬화 과정'을 거쳐서 구조체를 송수신하는 방법이 가장 좋다.
직렬화와 관련된 내용은 다음에 포스팅 하겠습니다!