테스트 환경

항목값
OSWindows 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타입은 논리연산시 다른 형식 대비 효율적으로 동작하여 미미한 비용 이득을 가진다.