constexpr을 선언하면 변수는 컴파일 타임에 사용된다고 알고 있었지만 아니었다. 결론만 쓰자면 컴파일러는 변수 쓰는 곳을 상수로 대체할 수도 있고 아닐 수도 있다.
다음을 보자.
constexpr auto v0 = 1u;
const auto v1 = 2u;
static_assert(v0 < v1);
auto p0 = std::addressof(v0);
auto p1 = std::addressof(v1);
static_assert(std::is_same_v<decltype(p0), decltype(p1)>);
// 값을 바꿔보자
const_cast<std::decay_t<decltype(v0)>&>(v0) = 2u;
static_assert(v0 < v1);
assert(v0 < v1);
// 어떻게 출력될까?
std::printf("%d %d\n", v0, v1);
std::printf("%d %d\n", *p0, *p1);
아래처럼 출력된다
1 2
2 2
메모리를 확인하면 마찬가지로 값이 변해있다.
0x000000BFC074F6D4 02 00 00 00 cc cc cc cc cc cc cc cc cc cc cc ....???????????
0x000000BFC074F6E3 cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc ???????????????
0x000000BFC074F6F2 cc cc 02 00 00 00 cc cc cc cc cc cc cc cc cc ??....?????????
어셈블리를 살펴보면 첫번째 printf에서 사용하는 인자의 경우 상수로 대체되었음을 알 수 있다.
std::printf("%d %d\n", v0, v1);
00007FF60A049960 mov r8d,2
00007FF60A049966 mov edx,1
00007FF60A04996B lea rcx,[string "%d %d\n" (07FF60A07BED4h)]
00007FF60A049972 call printf (07FF60A00F938h)
두번째 printf의 어셈블리를 보면 주소를 역참조하기 때문에 상수로 변환되지 않았음을 알 수 있다.
std::printf("%d %d\n", *p0, *p1);
00007FF60A049978 mov rax,qword ptr [p1]
00007FF60A04997C mov r8d,dword ptr [rax]
00007FF60A04997F mov rax,qword ptr [p0]
00007FF60A049983 mov edx,dword ptr [rax]
00007FF60A049985 lea rcx,[string "%d %d\n" (07FF60A07BED4h)]
00007FF60A04998C call printf (07FF60A00F938h)
그렇다면 배열의 경우는 어떨까? { 1, 2, 3 }을 메모리 조작을 통해 { 3, 2, 3 }으로 바꾼다.
// array
constexpr int a0[]{ 1, 2, 3 };
static_assert(a0[1] < a0[2]);
assert(a0[1] < a0[2]);
auto a1 = const_cast<int*>(a0);
a1[0] = 3;
static_assert(a0[0] < a0[1]);
assert(a0[0] > a0[1]);
printf("%d %d %d\n", a0[0], a0[1], a0[2]);
이를 출력하면 다음과 같다. 즉, 메모리의 값이 그대로 표시된다.
3 2 3
어셈블리를 보면 역시 상수로 대체되지 않았음을 알 수 있다
printf("%d %d %d\n", a0[0], a0[1], a0[2]);
00007FF666D4242D mov eax,4
00007FF666D42432 imul rax,rax,2
00007FF666D42436 mov ecx,4
00007FF666D4243B imul rcx,rcx,1
00007FF666D4243F mov edx,4
00007FF666D42444 imul rdx,rdx,0
00007FF666D42448 mov r9d,dword ptr a0[rax]
00007FF666D42450 mov r8d,dword ptr a0[rcx]
00007FF666D42458 mov edx,dword ptr a0[rdx]
00007FF666D4245F lea rcx,[string "%d %d %d\n" (07FF666D4B050h)]
00007FF666D42466 call printf (07FF666D411BDh)
이처럼 constexpr은 단순히 선언에 불과하며, 컴파일러에 따라 상수로 대체될 수도 있고 아닐 수도 있다. 따라서 복잡한 변수, 예를 들어 구조체 배열을 선언할 때 스택 영역에 생성하면 생성자/소멸자를 계속 호출할 수 있다. 이를 피하려면 static을 사용해야 한다.
'학교 > 정리' 카테고리의 다른 글
OpenTTD 빌드 (0) | 2024.05.12 |
---|---|
GPUView (0) | 2024.05.11 |
윈도우 가상 머신 다운로드 (0) | 2024.04.25 |