* Undefined Behavior를 처음 들어봤다면 먼저 이 글을 참조


#include <iostream>
#include <string.h>

int ub_fn(const unsigned char src) {
    bool dst;
    memcpy(&dst, &src1); // copy 1 byte from src to dst
    return dst ? 3 : 4;
}

int main(intchar**) {
    for (unsigned char i = 0i < 5i++) {
        std::cout << ub_fn(i) << "";
    }
    return 0;
}


이 코드의 실행 결과는?

머릿속으로 생각해본 뒤에 한번 정답과 비교해봐!

정답
g++ 11.2, -O0 (최적화 없음)

> 4, 3, 3, 3, 3, 


g++ 11.2, -O2 (최적화 적용)

> 4, 3, 2, 1, 0, 


clang++ 15.0.2, -O0 / O2

> 4, 3, 4, 3, 4, 


해설
이 문제의 핵심은 (좀 뻔하지만) memcpy에 있어. 정확히는 char에 들어 있던 데이터를 memcpy한 bool이 문제가 되는 거지만.


C++을 좀 다뤄본 챈럼이라면 아마 "정수형에서 bool로는 0이면 false, 그 외에는 true로 변환이 잘 정의돼 있는데 컴파일러가 잘못된 거 아닌가?" 라고 생각할 수도 있을 것 같아.

근데 문장을 잘 읽어봐야 하는 게, C++ 표준에서 변환이 잘 정의됐다는 건 변수에 직접 대입이나 초기화를 할 때, 즉 정해진 문법의 틀 안에서만 적용되는 이야기야. memcpy는 그냥 어떤 메모리 영역을 정해진 바이트 수만큼 다른 영역에 그대로 복사해버리니까, 말하자면 문법을 우회해버린다는 거지.

게다가 C++ 표준에서 bool의 크기는 사실 implementation-defined, 즉 컴파일러에서 정하기 나름이라고 명시돼 있어. 그러니까 1바이트일 수도 있고, 1비트일 수도 있고, 심지어 8바이트일 수도 있어. 최소 1바이트인 char를 bool에 memcpy했을 때 그게 bool 기준에서 제대로 된 값이란 보장이 없단 거야.


그러다 보니 g++의 경우는 bool을 int로 변환하는 경우도 true -> 1, false -> 0로 명확하게 정의돼 있단 점을 활용해서 return dst ? 3 : 4를 return 4 - dst 로 바꿔버리는 극단적인(?) 최적화를 해버렸는데 dst의 값이 전혀 다른 요상한 값이었다 보니 이상한 결과가 나오게 돼버렸어.

한편 clang++의 경우는 dst의 크기를 아예 1비트라고 간주한 건지 홀짝에 따라 3 4가 연속으로 나오는 결과를 일관적으로 보여주고 있어.


이렇게 undefined behavior를 일으키는 코드는 컴파일러에 따라, 또 같은 컴파일러 내에서도 최적화 여부에 따라 그 결과가 전혀 달라질 수 있어.

예전 글에는 UB가 뭔지 너무 이론적으로만 설명한 거 같았는데, 뭔가 눈에 확 띄는 예시를 발견해서 한번 들고 와봤어.