Do Statement (do-while)에 관한 소고
foreach가 나오기 이전에, 옛부터 프로그래밍에서 Loop을 돌릴 때 while, for, do-while 셋을 사용해 왔다. ㅎ
Loop을 돌릴 때 무엇을 사용하는가는 개발자 마음이겠지만, 오늘은 특별히 do-while에 대해서 좀 써보려 한다.
do-statement로 불려지기도 하는 do-while은 assembly 언어로 변환해 보면 for와 while보다 jump 연산이 1개 적게 쓰인다. 이 때문에 조금 빠르다고 할 수도 있다. (이것이 느껴지려면 우주만큼 루프를 돌리는 로직에 쓰였을 때인 것은 함정).
reference: http://stackoverflow.com/questions/2950931/for-vs-while-in-c-programming
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
int main(int argc, char* argv[]) { int i; char x[100]; //"FOR" LOOP: for(i=0; i<100; i++ ) { x[i] = 0; } //"WHILE" LOOP: i=0; while(i<100 ) { x[i++] = 0; } //"DO-WHILE" LOOP: i=0; do { x[i++] = 0; } while(i<100); return 0; } |
For Loop :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
010013C8 mov dword ptr [ebp-0Ch],0 010013CF jmp wmain+3Ah (10013DAh) for(i=0; i<100; i++ ) { x[i] = 0; 010013D1 mov eax,dword ptr [ebp-0Ch] <<< UPDATE i 010013D4 add eax,1 010013D7 mov dword ptr [ebp-0Ch],eax 010013DA cmp dword ptr [ebp-0Ch],64h <<< TEST 010013DE jge wmain+4Ah (10013EAh) <<< COND JUMP 010013E0 mov eax,dword ptr [ebp-0Ch] <<< DO THE JOB.. 010013E3 mov byte ptr [ebp+eax-78h],0 010013E8 jmp wmain+31h (10013D1h) <<< UNCOND JUMP } |
While Loop:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
i=0; 010013EA mov dword ptr [ebp-0Ch],0 while(i<100 ) { x[i++] = 0; 010013F1 cmp dword ptr [ebp-0Ch],64h <<< TEST 010013F5 jge wmain+6Ah (100140Ah) <<< COND JUMP 010013F7 mov eax,dword ptr [ebp-0Ch] <<< DO THE JOB.. 010013FA mov byte ptr [ebp+eax-78h],0 010013FF mov ecx,dword ptr [ebp-0Ch] <<< UPDATE i 01001402 add ecx,1 01001405 mov dword ptr [ebp-0Ch],ecx 01001408 jmp wmain+51h (10013F1h) <<< UNCOND JUMP } |
Do-While Loop:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
i=0; . 0100140A mov dword ptr [ebp-0Ch],0 do { x[i++] = 0; 01001411 mov eax,dword ptr [ebp-0Ch] <<< DO THE JOB.. 01001414 mov byte ptr [ebp+eax-78h],0 01001419 mov ecx,dword ptr [ebp-0Ch] <<< UPDATE i 0100141C add ecx,1 0100141F mov dword ptr [ebp-0Ch],ecx 01001422 cmp dword ptr [ebp-0Ch],64h <<< TEST 01001426 jl wmain+71h (1001411h) <<< COND JUMP } while(i<100); |
이러한 미묘한 속도 때문에 do-while을 쓰기에는 단점이 너무 많다.
무엇보다 비야네 할아버지가 do-statement는 가급적 쓰지말라고 하셨다. 그의 책 “The C++ Programming Language”에서 보면 이렇게 나온다. 원문은 아래와 같고,
In my experience, the do-statement is a source of errors and confusion. The reason is that its body is always executed once before the condition is evaluated. However, for the body to work correctly, something very much like the condition must hold even the first time through. More often
than I would have guessed, I have found that condition not to hold as expected either when the program was first written and tested or later after the code preceding it has been modified. I also prefer the condition ‘‘up front where I can see it.’’ Consequently, I tend to avoid do-statements.
이걸 대충 풀면,
내 경험상, do-statement 는 에러와 헷갈림의 근원이다. 왜냐하면 body 부분이 항상 먼저 한번 실행된 후에 조건이 수행되게 되기 때문이다. body 부분이 제대로 수행되기 위해서는 사실 최초 실행시에도 조건이 제대로 되어 있어야 한다. 내가 추측했던 것보다 더 꽤 자주 나는 그 조건들이 생각했던 대로 값을 가지고 있지 않았고, 그 점은 프로그램을 만들었을 때나 테스트할 때나, 수정하고 나서도 마찬가지였다. 그래서 난, 조건이 먼저 나와서 볼 수 있는 것을 더 선호한다. 결과적으로 나는 do-statement는 피하는 경향이 있다.
다른 여러 책에서도 do-while을 피하라는 말은 종종 나오는데 그 이유는 code readability 문제와 논리의 흐름 문제 때문이다.
1 2 3 4 5 6 7 8 9 10 11 12 |
// do_while_statement.cpp #include <stdio.h> int main() { int i = 0; do { printf_s("\n%d",i++); } while (i < 3); } |
do-statement는 code block이 반복 실행되는 조건이 하단에 위치한다. 일반적으로 개발자들은 위에서부터 아래로 코드를 읽어 내려오기 시작하는 경향이 있다. do-while문을 읽을 때에는 위에서 한번 실행되는 code block을 읽은 후에 하단에 조건을 확인하고, 다시 눈을 상단으로 얹어 어디서부터 무슨 조건으로 반복되는지를 확인하며 읽어 내려 와야 한다. 최소 아래 위로 두번은 읽어야 한다. 이것이 치명적으로 readability를 떨어뜨리고 머리 속에 한단 한단 쌓아 오던 논리를 꼬아버리는 효과를 불러 일으킨다.
아래 구문을 보면 더 헷갈릴 수 있다.
1 2 3 4 5 6 7 |
do { // ... continue; // ... } while (/* ... */); |
일반적으로 for나 while에 익숙한 많은 개발자들은 continue를 보는 순간 눈을 다시 위로 돌리며 loop code block의 처음으로 옮긴다. 그런데 do-statement에서는 어디로 가야할지 잠시 멍해진다. continue는 내부적으로 loop의 끝으로 jump 해 가는 것이지만, loop의 끝에 while이나 for는 아무것도 없으니 당연히 위로 가는 것에 익숙해져 있을 뿐이고, do while에서는 그게 헷갈리며 눈이 아래위로 왔다갔다 할 가능성이 많은 것이다.
do-while을 while이나 for로 고치려고 하는 과정에서 최초 1회 실행을 보장하기 위해 body를 두번 반복해서 쓰는 것은 어리석은 짓이다.
1 2 3 4 5 6 7 |
body while(...) { body |
Code logic을 잘 구성하면 do-while을 반복 없이 깔끔하게 while이나 for로 고칠 수 있다.