바이트 정렬 #
- 구조체나 클래스 안의 멤버 변수들은 가장 큰 크기의 멤버 변수를 기준으로 정렬되어 메모리에 저장된다.
- 왜 그럴까?
- CPU가 RAM에 접근하는 횟수를 줄이기 위해서다.
- 32bit 프로세서 컴퓨터가 한번에 가져올 수 있는 데이터 단위는 32bit다.
- 만약 데이터가 32bit 보다 작은데도 불구하고 두 단위에 걸쳐서 저장되어 있다면 두 번의 접근과 추가적인 연산이 필요해진다.
- 따라서 컴파일러는 패딩(Padding)을 추가해서 바이트 정렬을 수행한다.
- 멤버 변수들을 잘 정렬하면, 프로그램의 메모리 사용 효율을 높일 수 있다.
// 가장 큰 크기의 멤버 변수가 double이므로 8 byte 단위로 나뉘어 저장된다.
struct S
{
char b1; // 1 byte -> 처음 8 byte의 앞부분에 저장된다.
double b2; // 8 byte -> 처음 8 byte에 모두 못 담기므로, 두 번째 8 byte에 저장된다.
int b3; // 4 byte -> 세 번째 8 byte에 저장된다.
// ==> 총 24 byte
};
struct SS
{
// (두 번째, 세 번째 멤버 변수의 위치를 바꾸었다.)
char b1; // 1 byte -> 처음 8 byte의 앞부분에 저장된다.
int b3; // 4 byte -> 처음 8 byte의 뒷부분에 저장된다.
double b2; // 8 byte -> 두 번째 8 byte에 저장된다.
// ==> 총 16 byte
};
비트 필트 #
- 멤버 변수의 사이즈를 비트 단위로 명시하는 것이다.
- 비트 필드의 각 멤버는 최하위 비트(Least Significant Bit, LSB)부터 차례대로 배치된다.
- 비트 필드의 범위를 넘어서는 값은 저장하지 않는다.
- 원래 타입의 크기를 넘어서는 비트 필드를 사용하면 데이터는 타입 크기에 제한되고, 나머지는 패딩이 들어간다.
#include <iostream>
struct S
{
// 총 크기는 4 byte
unsigned int b : 3; // 3 bit 사이즈이므로 0 ~ 7 값만 저장할 수 있다.
};
int main()
{
S s = {6};
++s.b;
std::cout << s.b << '\n'; // 7
++s.b;
std::cout << s.b << '\n'; // 8은 범위를 넘어서므로, 0이 된다.
}
#include <iostream>
#include <cstdint>
#include <bit>
struct S
{
// 총 크기는 2 byte
unsigned char b1 : 3; // 3 bits
unsigned char : 2; // 2 bits -> 사용하지 않는다.
unsigned char b2 : 6; // 6 bits -> 처음 byte에 모두 들어갈 수 없으므로 두 번째 byte에 저장된다.
unsigned char b3 : 2; // 2 bits
};
int main()
{
std::cout << sizeof(S) << '\n';
S s;
s.b1 = 0b111;
s.b2 = 0b101111;
s.b3 = 0b11;
auto i = std::bit_cast<std::uint16_t>(s);
// usually prints 1110000011110111
// breakdown is: \_/\/\_/\____/\/
// b1 u a b2 b3
// u -> 사용하지 않는 부분
// a -> 컴파일러가 바이트 정렬을 위해 패딩을 넣은 부분
for (auto b = i; b; b >>= 1) // print LSB-first
std::cout << (b & 1);
std::cout << '\n';
}
References #