#[repr(...)] 속성

#![allow(unused)]
fn main() {
struct Foo {
    x: i32,
    y: i16
}
}

얼핏 보기엔 Foo의 크기는 6바이트 (32비트 + 16비트 (= 48비트))가 되야하지만, 실제론 8바이트가 됩니다.

이는 구조체가 메모리 상에 어떻게 저장되는지 알아야하는데, 결론부터 말하자면 6바이트가 아닌 8바이트가 되는 이유는 메모리 상에서 패딩을 적용하기 때문입니다.

패딩은 NULL 데이터가 삽입된것이며, 최대 크기인 x (32비트 = 4바이트)의 크기와 맞추기위해 y에 3 바이트(=24비트)의 패딩이 삽입되었습니다. 즉, y의 크기는 4바이트가 되었습니다.

이렇게 패딩을 넣어주는것이 메모리 정렬인데, 메모리 정렬이 필요한 이유는 CPU에 있습니다.

컴퓨터는 데이터를 쓰거나 읽을때, 워드(WORD) 단위로 처리됩니다. CPU 마다 다르긴 하지만, 1 WORD는 4바이트를 가집니다. 즉, 컴퓨터는 메모리를 4바이트 단위로 처리하죠. 처음에 본 Foo 구조체를 살펴봅시다.

첫번째 경우: 원본 (6 바이트)

x 32비트 + y 16비트 (= 48비트 = 6바이트)

만약 Foo의 데이터들이 0x03 부터 0x08 까지 메모리에 저장되어 있다고 가정해봅시다.

00 01 02 03 | 04 05 06 07 | 08 09 0A 0B | 0C 0D 0E 0F
         └──────────────────┘

CPU는 데이터를 처리하려면 WORD (= 4바이트) 단위로 처리해야합니다. 때문에 CPU는 총 3번의 메모리 접근을 해야합니다.

두번째 경우: 패딩 적용 (8 바이트)

x 32비트 + y (16비트 + 패딩 16비트 (= 32비트)) (= 64비트 = 8바이트)

이 경우엔 데이터가 정렬되었고, 때문에 메모리 상에 다음과 같이 저장됩니다.

00 01 02 03 | 04 05 06 07 | 08 09 0A 0B | 0C 0D 0E 0F
              └───────────────────────┘

이 경우엔 CPU는 총 2번의 메모리 접근을 해야합니다.

이처럼 위와같은 상황에선 두번째의 경우가 더 효율적입니다. 즉, 정렬을 위해선 1 WORD의 배수 크기로 데이터를 저장하면 됩니다. 즉, 구조체의 크기는 구조체에서 가장 큰 데이터의 크기의 배수가 됩니다.

#[repr(..)] 속성

#[repr(..)]은 구조체 또는 열거형의 메모리 레이아웃을 지정할 수 있는 속성입니다.

메모리 레이아웃은 크기, 정렬, 패딩 등이 포함됩니다.

  • #[repr(C)]: C/C++의 레이아웃을 따릅니다. 이는 FFI를 사용할때 유용합니다.
  • #[repr(packed)]: 패딩을 하지 않습니다. 이는 메모리 절약을 위해 사용되나, 앞서 말한 메모리 정렬이 필요한 상황에선 부정적인 영향이 있을 수 있습니다.
  • #[repr(transparent)]: 타입의 레이아웃을 필드 타입의 레이아웃으로 설정합니다. 필드는 하나만 제공되어야하며, ZST (Zero Sized Type)가 아니어야 합니다.
  • #[repr(align(n))]: 타입의 정렬을 n으로 설정합니다. n은 2의 거듭제곱이어야 합니다.
  • #[repr(u*)], #[repr(i*)]: 필드가 없는 열거형의 크기를 지정합니다. *8, 16, 32, 64, 128 입니다.

하나의 예시로, #[repr(u8)]을 사용하여, 열거형의 크기를 1바이트로 지정할 수 있습니다.

#[repr(u8)]

#![allow(unused)]
fn main() {
#[repr(u8)]
enum Color {
    Red,
    Green,
    Blue,
}

impl From<Color> for u8 {
    fn from(color: Color) -> u8 {
        unsafe { std::mem::transmute(color) }
    }
}

let color = Color::Red;
let value: u8 = color.into();

assert_eq!(value, 0);
}

#[repr(transparent)]

#![allow(unused)]
fn main() {
#[repr(transparent)]
struct Wrapper<T>(T);

assert_eq!(4, std::mem::size_of::<Wrapper<i32>>());
assert_eq!(1, std::mem::size_of::<Wrapper<u8>>());
}

자세한 내용은 nomicon 참조.