단순 클라이언트의 작업만을 생각했을 땐 구조체 혹은 클래스의 순서를 중요하게 생각하지 않았었다.
하지만 서버에서는 구조체, 클래스의 패딩 규칙이 중요하다 이에 따라서
구조체 전체 크기가 달라지기도 하기 때문이다.
구조체를 이용하여 새로운 자료형을 정의하여 사용할 때,
정의된 자료형은 컴파일러에 의해 데이터로 변환되고, 이어서 메모리에 저장된다.
컴퓨터 프로세서가 메모리에 '어떤 방식'으로 접근하여 데이터를 읽어가고
컴파일러가 프로그램 코드를 '어떤 규칙'으로 데이터로 변환하는지를 확인하여 보자
이를 통해, 처리속도가 빠르고 효율적으로 메모리를 사용하는 자료형을 구성할 수 있을 것이다.
1. 빈 구조체 혹은 클래스는 4바이트의 메모리 공간을 차지하게 된다.
2. 먼저 나열한 순서 대로 메모리 공간을 순차적으로 차지한다.
3. 각 자료형은 자신의 자료형을 배수로 하여 메모리 공간을 차지한다.
4. 실제 구조체 변수의 인스턴스화가 되었을 때,
공간을 차지하는 메모리의 총 공간은 멤버 중에서
가장 aligment가 가장 큰 멤버를 기준으로 배수의 값으로 결정된다.
프로세서의 메모리 접근 방식
32Bit 프로세서 컴퓨터에서 메모리에 데이터를 쓰거나, 읽을 때에는
4바이트(1 WORD) 단위로 접근하여 명령을 수행한다.
6바이트의 데이터가 메모리에 쓰여 있는 경우를 살펴보자
다음의 2가지 케이스에 대해서 프로세서는 메모리에 몇 번 접근해야 할까?
1번 케이스의 경우에 프로세서는 3번 메모리에 접근해야 하며,
2번 케이스의 경우에는 2번이다.
프로세서는 한 번에 4바이트(1 WORD) 씩 메모리에 접근하여 데이터를 읽어온다.
처음에는 0x00 - 0x03, 두 번째에는 0x04 - 0x07, 세 번재에는 0x08 - 0x0B의 메모리 주소에 접근한다.
이와 같은 데이터를 프로세서의 접근 방식에 맞추어
데이터를 메모리에 정렬하면 프로세서 접근 횟수를 줄여 처리속도를 향상시킬 수 있을 것이다.
그렇다면, 데이터를 효율적으로 정렬하기 위해서는 구조체를 어떻게 정렬해야 할까?
구조체의 크기를 1 WORD의 배수 크기로 맞추면 된다.
하지만, 구조체의 전체 크기를 1WORD 배수로 배추었음에도
실제로 디버깅해보면 구조체의 크기가 4바이트의 배수가 아닌 경우를 확인할 수 있다.
왜 그럴까?
컴파일러가 구조체를 다루는 규칙을 확인해보자.
Data Alignment & Structure Padding
컴파일러는 어떠한 규칙으로 코드를 데이터로 변환한다.
Data Structure Alignment는 컴파일러가 컴퓨터 메모리에 데이터를 저장할 때,
데이터를 정렬하는 규칙 또는 방식을 일컫는다.
이를 Data Alignment 또는 Memory Alignment라고 부르기도 한다.
Data Structure Padding은 컴파일러가 Alignment 규칙에 맞추어 데이터를 저장하기 위한 수단이다.
Alignment 규칙을 맞추기 위해, 컴파일러는 구조체를 컴파일하면서 의미없는 데이터를 추가한다.
이런 의미 없는 데이터를 패딩, Padding 데이터라고 부른다.
컴파일러가 패딩 데이터를 추가하는 규칙은 다음과 같다.
1. 구조체의 크기는 구조체 내 자료형 중, 가장 크기가 큰 자료형의 배수가 된다.
2. 구조체를 이루는 자료형 중, 가장 크기가 큰 자료형으로 데이터를 정렬한다.
CASE 1
typedef struct _MyData_
{
short Data1;
short Data2;
short Data3;
} MyData;
다음과 같은 규칙을 확인하면서 CASE1을 살펴보면,
가장 큰 자료형은 2바이트의 short형이다.
규칙 1에 다라, 이 구조체의 크기는 2바이트의 배수가 된다.
또한 규칙2에 따라, short 자료형으로 구조체를 정렬할 때, 3개의 변수가 모두 문제없이 정렬된다.
따라서 이 구조체의 크기는 6바이트가 된다.
구조체의 크기를 프로세서 처리 단위에 맞게 구성하기 위해서는
개발자가 명시적으로 Padding 데이터를 추가하는 코드를 작성하는 것이 좋다.
명시적으로 2바이트의 패딩 데이터를 다음과 같이 추가해보자.
typedef struct _MyData_
{
short Data1;
short Data2;
short Data3;
char temp[2];
} MyData;
CASE 2
typedef struct _MyData_
{
char Data1;
short Data2;
int Data3;
char Data4;
} MyData;
다음 CASE 2는
가장 큰 자료형은 4바이트의 int형이다.
규칙 1에 따라, 이 구조체의 크기는 4바이트의 배수가 된다.
또한 규칙2에 따라, 가장 큰 자료형인 int형으로 구조체를 정렬할 떄,
char형과 short형은 각각 1바이트와 2바이트로 4바이트 단위 정렬에 포함시킬 수 있다.
하지만 이어지는 int까지 4바이트 단위 정렬에 포함하기에는 남은 공간이 부족하다.
이렇게 되면
컴파일러는 Data Alignment를 맞추기 위해서
short와 int 사이에 암묵적인 패딩 1바이트를 넣고 하나의 4바이트 단위 메모리 구성을 마무리한다.
그리고 새로운 4바이트 단위의 메모리에서 int형을 포함시킨다.
남은 char형은 또 그 다음에 오는 4바이트 메모리 공간에 포함시키고
3바이트의 패딩 데이터로 남은 공간을 채운다.
따라서, CASE2의 구조체 크기는 12바이트가 된다.
이 구조체를 수정해서 패딩 데이터로 낭비되는 메모리 공간을 없애보자.
typedef struct _MyData_
{
char Data1;
char Data2;
short Data3;
int Data4;
} MyData;
메모리에 어떻게 저장되어 있는지 살펴보자면,
12바이트의 크기가 8바이트로 감소했다.
Data Alignment 규칙에 따라, 가장 큰 자료형의 크기에 맞추어 구조체를 구성하면
패딩데이터를 줄여 메모리를 효율적으로 사용할 수 있다.
위의 내용을 최종적으로 정리하여 이야기해보자면,
프로세서의 처리성능과 메모리를 효율적으로 사용하기 위해서는
1. 구조체 안의 자료형을 가장 큰 자료형의 크기에 맞추어 최대한 포함시킨다. (Data Alignment)
2. 구조체의 크기가 32비트 프로세서의 처리단위인 4바이트 (1WORD)의 배수가 아니면,
Padding 데이터를 추가하여 명시적으로 크기를 맞추어준다. (Data Structure Padding)
비트필드구조체
비트필드는 구조체와 흡사하나 구조체는 바이트 단위로 멤버를 사용할 수 있지만,
비트필드는 비트단위로 멤버를 사용할 수 있다.
그래서 많은 상태를 저장하거나 비트별로 제어해야 하는 경우에 비트 필드를 사용한다.
비트 필드는 멤버의 unsined형 (unsigned int형)과 int형의 멤버를 가질 수 있고,
비트 필드 변수의 크기는 int형 크기와 같은 4바이트(32비트)이다.
signed와 unsigned는 char, short, int, long 등의 자료형 앞에 사용되어 부호가 있는 정수와
부호가 없는 정수를 나타내는 자료형으로 사용되며 signed는 보통 생략하여 사용한다.
비트 필드처럼 unsigned가 단독으로 사용하면 unsigned int형을 간략하게 표현한 형태이다.
#include <stdio.h>
#include <string.h>
typedef struct _bitfield{
unsigned b0 : 1;
unsigned b1 : 1;
unsigned b2 : 1;
unsigned b3 : 1;
unsigned b4 : 1;
unsigned b5 : 1;
unsigned b6 : 1;
unsigned b7 : 1;
} bitfield;
int main(int argc, const char * argv[]) {
bitfield bit;
memset(&bit, 0, sizeof(bitfield));
bit.b0 = 1;
bit.b7 = 1;
printf("%d\n", sizeof(bitfield));
printf("%08x %d\n", bit, bit);
return 0;
}
/* 결과
4
00000081 129
*/
출처: https://nomad-programmer.tistory.com/355 [nomad-programmer:티스토리]
참고 사이트
[Programming/C] 비트 단위로 제어하기 (Struct Bit Field 활용) (tistory.com)
외 티스트리 블로그 1개
'프로그래밍 언어 > C & C++ 정리' 카테고리의 다른 글
재귀함수 (1) | 2024.04.19 |
---|---|
L-value, R-value (0) | 2024.04.11 |
const int *, const int* const, int* const (0) | 2024.04.09 |
다이나믹 캐스트 (0) | 2024.04.04 |
C++ 복사 생성자, 이동 생성자, Push_back, emplace_back (0) | 2024.04.01 |