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++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.