※ 아래 어셈블리 구문들은 Intel 64 bit, Visual Studio 2013 Community 버전에서 컴파일되었습니다.
switch-case 구문은 C/C++에서 조건문이 상수인 경우에 사용할 수 있는 조건 분기문이다. 조건문에 변수도 사용할 수 있는 if 구문의 특수한 형태라고 할 수 있는데 대개 처음 C/C++을 배울 때 특정 조건에서 switch 구문의 우월함을 같이 접하곤 한다. 정말인지 다음 코드를 보자.
양쪽 모두 n이 3으로 시작하며 1일 때, 2일 때를 거쳐 종료하는 로직이다. 이 코드를 기본 릴리즈 환경으로 빌드하면 대개 속도 최적화가 적용된 어셈블리 코드가 나오는데 먼저 if 구문의 코드부터 살펴보자.
처음 보는 사람은 약간 복잡해보일 수 있지만 우리가 이미 원래의 코드를 알고 있으므로 더듬더듬 추적해볼 수 있을 것이다. 위 코드를 흐름 그대로 따라간다면 다음과 같다.
1. eax 레지스터에 3을 대입한다.
2. eax 레지스터와 1을 비교한다. (=eax 레지스터에서 1을 뺀다)
3. (그 결과값이 0과) 같지 않으므로 0x00BB1026 주소로 점프한다.
4. eax 레지스터와 2를 비교한다. (=eax 레지스터에서 2를 뺀다)
5. (그 결과값이 0과) 같지 않으므로 0x00BB103C 주소로 점프한다.
6. eax 레지스터와 3을 비교한다. (=eax 레지스터에서 3을 뺀다)
7. (그 결과값이 0과) 같으므로 printf("echo 3")를 수행한다.
8. eax 레지스터에 1을 대입한다.
9. 무조건 0x00BB1010 주소로 점프한다.
10. eax 레지스터와 1을 비교한다.
11. 같으므로 printf("echo 1")을 수행한다.
12. eax 레지스터에 2를 대입한다.
13. 무조건 0x00BB1010 주소로 점프한다.
14. eax 레지스터와 1을 비교한다.
15. 같지 않으므로 0x00BB1026 주소로 점프한다.
16. eax 레지스터와 2를 비교한다.
17. 같으므로 printf("echo 2")를 수행한다.
18. eax 레지스터에 4를 대입한다.
19. 무조건 0x00BB1010 주소로 점프한다.
20. eax 레지스터와 1을 비교한다.
21. 같지 않으므로 0x00BB1026 주소로 점프한다.
22. eax 레지스터와 2를 비교한다.
23. 같지 않으므로 0x00BB103C 주소로 점프한다.
24. eax 레지스터와 3을 비교한다.
25. 같지 않으므로 0x00BB1052 주소로 점프한다.
26. 끝.
위와 같은 방법으로 다시 한 번 추적해보자.
1. eax 레지스터에 3을 대입한다.
2. eax 레지스터에서 1을 뺀다. (2가 됨)
3. 그 결과값이 0과 같지 않으므로 점프하지 않는다.
4. eax 레지스터에서 1을 뺀다. (1이 됨)
5. 그 결과값이 0과 같지 않으므로 점프하지 않는다.
6. eax 레지스터에서 1을 뺀다. (0이 됨)
7. 그 결과값이 0과 같으므로 점프하지 않는다.
8. printf("echo 3")을 수행한다.
9. eax 레지스터에 1을 대입한다.
10. 무조건 0x00BB1070 주소로 점프한다.
11. eax 레지스터에서 1을 뺀다. (0이 됨)
12. 그 결과값이 0과 같으므로 0x00BB109B 주소로 점프한다.
13. printf("echo 1")을 수행한다.
14. eax 레지스터에 2를 대입한다.
15. 무조건 0x00BB1070 주소로 점프한다.
16. eax 레지스터에서 1을 뺀다. (1이 됨)
17. 그 결과값이 0과 같지 않으므로 점프하지 않는다.
18. eax 레지스터에서 1을 뺀다. (0이 됨)
19. 그 결과값이 0과 같으므로 0x00BB108A 주소로 점프한다.
20. printf("echo 2")를 수행한다.
21. eax 레지스터에 4를 대입한다.
22. 무조건 0x00BB1070 주소로 점프한다.
23. eax 레지스터에서 1을 뺀다. (3이 됨)
24. 그 결과값이 0과 같지 않으므로 점프하지 않는다.
25. eax 레지스터에서 1을 뺀다. (2가 됨)
26. 그 결과값이 0과 같지 않으므로 점프하지 않는다.
27. eax 레지스터에서 1을 뺀다. (1이 됨)
28. 그 결과값이 0과 같지 않으므로 0x00BB10AC 주소로 점프한다.
29. 끝.
-----------------------------------------------------------------------------------------
덧 : 글을 저지르고 읽다보니 여전히 이견이 있을 수 있어 내용을 추가한다. (친절함이 상당수 사라져 그림이 없다)
switch문의 경우 case의 개수가 일정 이상 커지면 지금 보는 모양과는 전혀 다른 모양을 갖는데(일명 점프 테이블이라고 한다) 대개 switch문이 if문보다 효율이 좋다고 말하는 근거에 해당한다. 기준이 되는 case의 개수는 컴파일러에 따라 다르다.
if문의 최적화 역시 마찬가지로 switch문처럼 일정 이상 커지면 점프 테이블을 구성해주기도 하는데 이 역시 컴파일러러에 의존적이다. 점프 테이블은 모르오 같은 컴파일러도 있다(안타깝게도 VS 컴파일러가 상당수 포함된다). 위에서는 jne 인스트럭션을 사용했지만 if문을 사용하더라도 switch문처럼 je 인스트럭션을 사용하도록 코드를 작성할 수도 있다(코드 유지보수상 권장하지는 않는다).
이도저도 복잡하고 잘 모르겠고 귀찮고 고민할 시간적 비용이 나오지 않는다면, 그냥 switch를 쓰는 것이 무방하다. 물론 굉장한 구형 컴파일러를 만난다면 switch 구문도 어마어마한 비효율 코드를 발생시키기도 하지만, 나처럼 이런 글까지 써놓고도 컴파일러를 크게 신뢰하지 않는 것도 하나의 방법이라 하겠다.
(좋은 시간 낭비의 사례)