Info
해당 글에서는 다음과 같은 것들을 다룬다.
switch,case내부에서 왜 변수 선언이 안되는지switch,case의 디스어셈블 결과확인 및 분석braces내부에서 선언된 변수의 어셈블리 코드 분석
왜 switch, case내부에서 왜 변수 선언이 안될까?
switch, case 문법은 하나의 braces의 묶음으로 이루어진다.
case label들은 goto문법과 같이 서로의 영역을 건너뛰어 동작하지만, 컴파일러 입장에서는 braces단위로 영역을 구분하기 때문에 비록 다른 label에서 선언되고 초기화된다해도(혹은 건너뛴다고 해도) 여기에 접근할 수 있다고 여기게 된다.
C++ standard 표준은 초기화를 건너뛴 변수에 접근할 수 있는 상황을 허용하지 않으므로 에러가 발생하게 된다.
int type = 2;
switch (type) {
case 1:
int x = 100; // 'x' 선언 및 초기화
// ... x 사용 ...
break;
case 2: // 'type'이 2면 여기로 바로 점프
// 여기서 'x'는 스코프 내에 있지만, case 1을 건너뛰었으므로
// 초기화되지 않은 상태로 접근될 위험이 있다.
// std::cout << x << std::endl; // 컴파일 에러!
break;
}switch, case의 디스어셈블 결과확인 및 분석
그렇다면 switch, case 내부에서 선언된 변수는 어셈블리로 어떻게 처리될까?
아래 코드를 살펴보자.
__attribute__((noinline)) // Prevent inlining
int test(int choice) {
int ret;
switch (choice) {
case 1: {
volatile int temp_val_case1 = 100;
ret = temp_val_case1 + 5;
break;
}
case 2: {
volatile int temp_val_case2 = 200;
ret = temp_val_case2 + 10;
break;
}
default: {
volatile int temp_val_default = -1;
ret = temp_val_default;
break;
}
}
return ret;
} test:
341808b0: cmp r0, #1
220 int test(int choice) {
341808b2: sub sp, #16
222 switch (choice) {
341808b4: beq.n 0x341808d2 <test+34>
341808b6: cmp r0, #2
341808b8: bne.n 0x341808c6 <test+22>
229 volatile int temp_val_case2 = 200;
341808ba: movs r3, #200 @ 0xc8
341808bc: str r3, [sp, #8]
230 ret = temp_val_case2 + 10;
341808be: ldr r0, [sp, #8]
341808c0: adds r0, #10
240 }
341808c2: add sp, #16
341808c4: bx lr
234 volatile int temp_val_default = -1;
341808c6: mov.w r3, #4294967295
341808ca: str r3, [sp, #12]
235 ret = temp_val_default;
341808cc: ldr r0, [sp, #12]
240 }
341808ce: add sp, #16
341808d0: bx lr
224 volatile int temp_val_case1 = 100;
341808d2: movs r3, #100 @ 0x64
341808d4: str r3, [sp, #4]
225 ret = temp_val_case1 + 5;
341808d6: ldr r0, [sp, #4]
341808d8: adds r0, #5
240 }
341808da: add sp, #16
341808dc: bx lr
341808de: nop 먼저 변수의 저장에 대해서 확인해보자.
어셈블리 코드를 살펴보면 다음과 같은데
sub sp, #16어떤 braces에 위치하던 관계 없이 함수의 시작부에서 모두 할당하는 것을 볼 수 있다.
braces 내부에서 선언된 변수의 어셈블리 코드 분석
평소 braces 내부에 변수를 선언하면 braces를 벗어날 경우, 스택 공간이 다시 해제되어 공간적 효율을 가질 수 있을 것으로 생각했었다.
이것이 잘못된 것임을 추가로 검증하고자한다.
다음 코드를 살펴보자.
__attribute__((noinline)) // Prevent inlining
int test(int choice) {
int ret;
{
volatile int a = 10;
ret = a;
}
{
volatile int a = 20;
ret = a;
}
{
volatile int a = 30;
ret = a;
}
return ret;
} test:
341808b0: movs r1, #10
227 volatile int a = 20;
341808b2: movs r2, #20
231 volatile int a = 30;
341808b4: movs r3, #30
220 int test(int choice) {
341808b6: sub sp, #16
223 volatile int a = 10;
341808b8: str r1, [sp, #4]
224 ret = a;
341808ba: ldr r1, [sp, #4]
227 volatile int a = 20;
341808bc: str r2, [sp, #8]
228 ret = a;
341808be: ldr r2, [sp, #8]
341808c0: str r3, [sp, #12]
232 ret = a;
341808c2: ldr r0, [sp, #12]
235 }
341808c4: add sp, #16
341808c6: bx lrswitch case 때와 마찬가지로 braces와 관계 없이 할당된 데이터 만큼의 스택을 할당하는 것을 확인할 수 있다.
341808b6: sub sp, #16즉, braces내부에 위치하더라도 함수 시작시 모든 변수가 사전에 할당된다.
테스트 환경
OS: Window11
IDE & ToolChain : STM32Cube
Board: STM32N6-DK