테스트 환경
| 항목 | 값 |
|---|---|
| OS | Windows 11 Pro |
| 컴파일러 | g++ 14.2.0 |
| 어셈블리 옵션 | -std=c++20 -O2 -S -masm=intel |
내용
이번 글에서는 bool이 int와 같은 형식과 어떤식으로 다르게 연산되며 실제로 어떤 크기를 가지는지에 대해서 알아보겠다.
이전에 bool타입은 결국 int와 같이 4바이트로 처리되며 연산도 int와 결국 다르지 않다는 말을 들었는데 그걸 검증하기 위함이다.
바로 예제를 보자.
#define AF extern "C" __attribute__((noinline))
AF void store_bool(bool* p) { *p = true; }
AF void store_int(int* p) { *p = 1; }
AF int promote_bool(bool* p) { return 10 + *p; }
AF int promote_int(int* p) { return 10 + *p; }
AF bool eq_false(bool b) { return b == false; }
AF bool eq_true(bool b) { return b == true; }
AF bool eq_zero_int(int x) { return x == false; }
AF bool eq_one_int(int x) { return x == true; }
int main() {
volatile bool a = false;
volatile int b = 0;
return a | b;
}해당 코드에 해당하는 asm코드는 다음과 같다.
.file "main.cpp"
.intel_syntax noprefix
.text
.p2align 4
.globl store_bool
.def store_bool; .scl 2; .type 32; .endef
store_bool:
mov BYTE PTR [rcx], 1
ret
.p2align 4
.globl store_int
.def store_int; .scl 2; .type 32; .endef
store_int:
mov DWORD PTR [rcx], 1
ret
.p2align 4
.globl promote_bool
.def promote_bool; .scl 2; .type 32; .endef
promote_bool:
movzx eax, BYTE PTR [rcx]
add eax, 10
ret
.p2align 4
.globl promote_int
.def promote_int; .scl 2; .type 32; .endef
promote_int:
mov eax, DWORD PTR [rcx]
add eax, 10
ret
.p2align 4
.globl eq_false
.def eq_false; .scl 2; .type 32; .endef
eq_false:
mov eax, ecx
xor eax, 1
ret
.p2align 4
.globl eq_true
.def eq_true; .scl 2; .type 32; .endef
eq_true:
mov eax, ecx
ret
.p2align 4
.globl eq_zero_int
.def eq_zero_int; .scl 2; .type 32; .endef
eq_zero_int:
test ecx, ecx
sete al
ret
.p2align 4
.globl eq_one_int
.def eq_one_int; .scl 2; .type 32; .endef
eq_one_int:
cmp ecx, 1
sete al
ret
.section .text.startup,"x"
.p2align 4
.globl main
.def main; .scl 2; .type 32; .endef
main:
sub rsp, 56
call __main
mov BYTE PTR 43[rsp], 0
mov DWORD PTR 44[rsp], 0
movzx eax, BYTE PTR 43[rsp]
mov edx, DWORD PTR 44[rsp]
add rsp, 56
or eax, edx
ret
.def __main; .scl 2; .type 32; .endef
.ident "GCC: (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders, r3) 14.2.0"이를 기반으로 궁금했던 것들이 어떻게 동작하는지 살펴보자.
bool타입의 크기
다음 명령에 의해 rsp + 42 위치에 1byte크기로 0을 초기화하는 것을 볼 수 있다.
mov BYTE PTR 43[rsp], 0즉, bool은 1바이트의 스택 공간을 차지하는 것이 맞다.
bool타입의 사칙연산
다음 명령에 의해 eax레지스터 즉, 32비트 레지스터에 zero extension(zx)되어 연산되는 것을 확인할 수 있다.
promote_bool:
movzx eax, BYTE PTR [rcx]
add eax, 10
ret그렇다면 int형식은 어떨까?
다음 명령에 의해 bool과 동일하게 eax레지스터에 옮겨져 연산이 수행되는 것을 확인할 수 있다.
promote_int:
mov eax, DWORD PTR [rcx]
add eax, 10
ret하지만 당연하게도 bool과 달리 extension은 일어나지 않는다.
즉, bool은 사칙연산시 32bit로 승격되어 연산된다는 것을 확인할 수 있다.
bool타입의 논리연산
다음 명령에 의해 eax레지스터로 인자를 옮기고 xor명령으로 연산하게된다.
eq_false:
mov eax, ecx
xor eax, 1
ret이는 1사이클에 가까울 정도로 매우 낮음 비용을 소모한다.
그렇다면 int타입의 논리연산은 어떨까? 결과가 동일할거라고 예상한 것과 달리 차이를 가졌다.
다음 명령에 의해 먼저, test명령으로 ecx & ecx를 수행하고 결과를 ZF(zero flag)에 저장하도록한다. 그리고 이를 sete명령으로 확인 후, al레지스터에 0 혹은 1을 결과에 따라 저장한다.
eq_zero_int:
test ecx, ecx
sete al
ret이는 이전의 bool타입 대비 1 ~ 2사이클을 더 소모하게된다.
결론
bool타입은 1바이트의 크기를 가진다.bool타입은 사칙연산시 승격이 발생하여 미미한 추가 사이클 소모를 가진다.bool타입은 논리연산시 다른 형식 대비 효율적으로 동작하여 미미한 비용 이득을 가진다.