34. 엄격한 앨리어싱(strict aliasing)과 타입 퍼닝(type punning)

난이도 상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C++
// 주제: 엄격한 앨리어싱(strict aliasing) 규칙과 타입 퍼닝(type punning)
//
// 게임 서버는 네트워크 버퍼(바이트 배열)를 구조체로 "캐스팅"하거나, float의
// 비트 패턴을 정수로 꺼내 해시/직렬화하는 코드를 자주 만난다. 아래 세 가지 방식을
// 비교한다.
//
// 과제:
//  1) reinterpretPun / memcpyPun / unionPun 세 함수가 각각 무엇을 반환하는지,
//     그리고 세 방식이 C++ 표준 관점에서 어떻게 다른지(정의됨/미정의) 설명하라.
//  2) aliasingDemo 는 -O0 과 -O2 에서 각각 무엇을 출력할 수 있는가?
//     컴파일러가 무슨 가정을 하기에 최적화 레벨에 따라 결과가 달라질 수 있는지
//     "엄격한 앨리어싱 규칙" 관점에서 설명하라.
//  3) 네트워크 버퍼를 안전하게 구조체로 해석하는 올바른 방법은 무엇인가?
//
// (동작을 예측한 뒤 컴파일/실행해 확인하라. 내부 동작 설명은 answer.md 참조.)

#include <cstdint>
#include <cstring>
#include <cstdio>

// (1) reinterpret_cast 로 float 비트를 uint32_t 로 읽는다.
uint32_t reinterpretPun(float f) {
    return *reinterpret_cast<uint32_t*>(&f);
}

// (2) memcpy 로 비트를 복사한다.
uint32_t memcpyPun(float f) {
    uint32_t out;
    std::memcpy(&out, &f, sizeof(out));
    return out;
}

// (3) union 에 float 로 쓰고 uint32_t 로 읽는다.
union FloatBits { float f; uint32_t u; };
uint32_t unionPun(float f) {
    FloatBits b;
    b.f = f;
    return b.u;
}

// 서로 다른 타입 포인터가 같은 메모리를 가리킬 때.
int aliasingDemo(int* pi, float* pf) {
    *pi = 42;
    *pf = 1.0f;      // pi 와 같은 주소를 가리킬 수 있다
    return *pi;      // 42 를 반환할까, 다른 값일까?
}

int main() {
    float x = 1.0f;  // IEEE-754: 0x3F800000
    std::printf("reinterpret = 0x%08X\n", reinterpretPun(x));
    std::printf("memcpy      = 0x%08X\n", memcpyPun(x));
    std::printf("union       = 0x%08X\n", unionPun(x));

    int storage = 0;
    int r = aliasingDemo(&storage, reinterpret_cast<float*>(&storage));
    std::printf("aliasingDemo returns %d\n", r);
    return 0;
}
내 리뷰 · C++
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.