본문 바로가기
학교/정리

constexpr 선언 시 저장 위치

by ehei 2024. 5. 30.

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