제5장 디버깅과 추적 문봉근
제5장 디버깅과 추적 5.1 디버깅 5.2 에러메시지 종류와 의미 5.3 추적-1 5.4 추적-2
5.1 디버깅 디버깅 프로그램 작성 -> 컴파일 -> 에러 메시지 출력 - 에러 메시지를 바탕으로 프로그램 수정 디버깅 프로그램 작성 -> 컴파일 -> 에러 메시지 출력 - 에러 메시지를 바탕으로 프로그램 수정 - 에러가 없을 때까지 컴파일 과정 반복 - 에러 메시지의 이해가 프로그램 작성에 많은 도움 - 에러들의 수정 과정을 디버깅(debugging)라 함 - 에러 메시지의 의미를 반드시 알아야 한다. - C 언어의 에러 메시지의 종류와 의미를 설명한다.
예제 5.1 다음 프로그램에서 printf를 print로 바꾸어 보자. ☑ 프로그램 #include <stdio.h> void main() { print("C언어를 배웁니다.."); }
그림 5.1 에러 출력 화면 예제 5.1을 실행시키면 그림 5.1과 같은 화면 D:\Sample\ex1.c(4): error C2065: ‘print' : undeclared identifier - 이 메시지는 ex1.c 파일에서 4번째 줄에 print가 선언되지 않은 식별자(undeclared identifier)라는 에러 메시지이다. 이때 에러 라인에서 더블클릭하면 프로그램의 4번째 줄로 커서가 자동으로 이동한다.
예제 5.2 문장을 하나 추가하고 세미콜론(;)을 제거해 보자. ☑ 프로그램 //ex1.c #include <stdio.h> void main() { printf("C언어를 배웁니다..") /* “;” 제거 */ printf("에러 찾기입니다. "); }
☑ 실행결과 ☑ 설명 프로그램을 실행 후 에러 메시지 error C2146: syntax error: missing ';' before identifier 'printf' Error executing cl.exe. ex1.exe - 1 error(s), 0 warning(s) ☑ 설명 내용을 잘 살펴보면 한 개의 에러가 발생했는데 ① printf 앞에서(before) ② 세미콜론(;)이 ③ 빠졌다(missing)는 에러 메시지이다.
5.2 에러메시지 종류와 의미 ■ warning C4013: ‘printf' undefined; assuming extern returning int printf 문을 호출하기 위한 #include <stdio.h>을 선언하지 않았다. ■ ‘k' : undeclared identifier - 변수 k가 선언되지 않았다. 즉 선언(int, float 등)에 변수가 없다. ■ syntax error : missing ‘)' before ‘;' - 세미콜론 앞에 ‘)’가 빠졌다. ■ ( expected ) expected , expected < expected { expected } expected - (, ), “, ", <, {, }, :, ; 등이 있어야한다. 즉 이 기호가 빠졌다는 의미.
continue ■ Declaration syntax error - 프로그램 선언부(int, float, char)에서 symbol 이 빠졌거나 추가가 필요하다는 의미. 현재의 라인이나 그 이전의 라인에서 세미콜론(;)이나 괄호({,}) 등을 검사해야 한다. ■ Undefined symbol ‘identifier' - 사용된 변수 등이 선언부에 빠졌다. - 또한 이와 같은 메시지는 철자의 오류가 많다. ■ Case statement missing : - case 문은 다음에 “:"이 나와야 한다. 예) case 'A' : ■ Declaration is not allowed here - while, for, do, if, switch 등의 제어문에는 선언문이 올 수 없다.
continue ■ Declaration missing ; - struct나 union 선언문에는 반드시 끝에 세미콜론(;)이 나와야 한다. ■ Declaration terminated incorrectly - 함수에서 세미콜론과 같이 잘못 위치하여 프로그램에 오류가 발생하여 종료. ■ Declaration was expected - 여기에 필요한 부호가 빠졌다. 즉, 콤머(,), 세미콜론(;), 괄호((,),{,})등이 빠짐. ■ Invalid indirection - 간접 연산자(*)는 void pointer 선언이 필요 없다. 예) int main (void) { void *p; *p = 10; /* ERROR: Invalid Indirection */ return 0; } ■ Multiple declaration for ‘identifier' - identifier(변수나 함수 등)가 한번 이상 또는 중복 선언되었다. int a; float a;
continue ■ Unable to open include file ‘filename' - 컴파일러가 명명한 파일 이름을 찾을 수 없다. - 이 경우는 파일이 존재하지 않거나 경로가 올바르지 않는 경우. ■ Undefined label ‘identifier' - goto 문과 상응하는 label의 정의가 없다. ■ Compound statement missing } - 중괄호({, })의 불일치가 일어났다. ■ Cannot cast from ‘type1' to ‘type2' - type 1과 type 2가 서로 일치하지 않는다. - 포인터는 정수형과 실수형으로 cast할 수 있지만 구조체나 배열은 어느 것으로도 cast할 수 없다. ■ Misplaced break - break문은 switch 문이나 루프 안에서만 사용된다.
continue ■ Misplaced continue - continue 문은 루프 안에서만 사용이 가능하다. ■ Ambiguity between ‘function1' and ‘function2' Address of overloaded function ‘function' doesn't match ‘type' - 함수사이의 변수가 일치하지 않는다. ■ Array bounds missing ] OR Delete array size missing ] OR Operator [] missing ] OR Subscripting missing ] - “]"가 빠졌다. “["가 연산자로 선언되었다. ■ Array must have at least one element - 배열은 하나 이상의 요소가 함께 선언되어야 한다. ■ char ray[]; /* definition of unknown size -- ILLEGAL */ char ray[0]; /* definition of 0 size -- ILLEGAL */ extern char ray[]; /* declaration of unknown size -- OK */ Array size too large - 배열 크기가 너무 크게 선언되었다. 예) int huge array[70000L]; /* Allocate 140000 bytes */
continue ■ Cannot overload ‘main' - main이 한번이상 쓰였다. ■ Undefined symbol ‘identifier' - identifier가 선언되지 않았다. ■ Abnormal program termination - 이 에러는 주로 메모리가 부족하여 발생한다. ■ Divide error Floating point error: Divide by 0 - 0으로 나누었을 때 발생한다.
5.3 추적-1 추적(trace) = 프로그램을 컴파일 후 에러는 없지만 원하는 결과가 나오지 않았을 때, 즉 논리적인 에러인 경우에 한 라인씩 실행해 가면서 변수들의 값들이 어떻게 변하는지 추적하여 에러를 찾는 것. 추적(trace)은 컴파일이 완성되어 문법 에러(syntax error)가 없는 경우, 즉 실행파일(*.exe)을 만든 후 추적기능을 사용해야 한다.
그림 5.1 추적하기 위한 선택 화면 그림 5.2 추적하기 위한 선택 화면
기능 단축키 의미 Go F5 끝까지 실행 Step Into F11 한 라인씩 실행 Run to Cursor Ctrl+F10 커서 위치까지 실행 그림 5.2처럼 추적은 다음과 같이 3가지 기능이 있다.
1에서 10까지의 합을 구하는 프로그램 코딩을 잘못하여 sum=sum+i;를 sum=sum-i;로 잘못된 경우이다. 그림 5.3 코딩의 실수에 의한 프로그램 1에서 10까지의 합을 구하는 프로그램 코딩을 잘못하여 sum=sum+i;를 sum=sum-i;로 잘못된 경우이다. 컴파일을 하면 에러(error)와 경고(warning)는 없지만 실행하면 결과가 +55가 아닌 -55가 나타난다. 이제 추적을 실행해보자.
그림 5.4 추적의 첫 실행 화면 F11 키를 누르면 프로그램 처음 “{“ 앞에 포인터(화살표)가 나타난다. 시작 포인터를 의미. F11을 계속 누르면 한 라인씩 실행.
그림 5.5 F11을 눌렀을 때의 화면 여기서 F11을 한번 눌러보자. 그러면 그림 5.5와 같은 화면이 나타난다 그림 5.5는 변수 n, i, sum의 선언을 알리고 각각의 값이 선언 당시의 메모리에 있는 값들을 보여준다.
이때 다시 F11을 눌러 이 라인을 실행하면 그림 5.5와 같다. 그림 5.6에서 처럼 n=10, sum=0이 들어있는 것을 알 수 있다.
그림 5.7 for loop를 세 번 반복한 후의 화면 F11을 계속 누르면서 실행해 보면 i와 sum의 값이 변하는데 따라서 문장 sum=sum-i;가 잘못됨을 알 수 있다. 논리적 에러를 발견하면 나머지 추적은 필요 없기 때문에 F5를 눌러 끝가지 실행시키고 코드를 수정하면 된다.
만일 F11을 계속 누르면 프로그램이 종료하기 위하여 Visual C++의 “FileView”를 누르고 “ex1.c”를 찾아 더블클릭하면 된다.
그림 5.9 코딩의 실수에 의한 화면 Ctrl+F10(커서 위치까지 실행)의 기능을 알아보자. 그림 5.9는 1-100까지의 합과 1-100까지의 홀수의 합을 구하는 프로그램이다. 1-100의 합은 이상이 없는데 100까지의 홀수의 합은 -2500이라는 결과가 나온다. 즉, “odd=odd+j;”가 “odd=odd-j;”로 잘못 코딩된 경우이다.
이 경우 trace를 하기 위하여 F11을 누르면 첫 번째 for문인 for(i=1;i<=n;i++) sum=sum+i; 여기에서 F11을 100번 눌러야 하는 경우 발생. 따라서 F11을 누르다가 이상이 없는 경우에는 for문을 빠져 나와야 한다. 이때 사용하는 기능이 Ctrl+F10 이다.
그림 5.10 Ct기+ F10키를 이용한 추적위치 옮기기 Ctrl+F10 을 위해 커서를 첫 번째 for문이 끝나는 다음 문장 즉, for(j=1; j<=n; j+=2) 앞에 마우스를 클릭하여 Ctrl+F10을 누르면 이전의 for문을 모두 실행시키고 커서위치로 돌아온다. 따라서 그림 5.9는 Ctrl+F10은 실행한 결과이기 때문에 sum=5050 결과가 나타나는 것이다. 이제 F11을 이용하여 홀수의 합을 구하는 문장을 추적해보면 odd 결과가 잘못됨을 알 수 있다. 따라서 C 에서는 앞에서 설명한 세 가지 추적 기능만 가지고도 논리적 에러를 찾을 수 있다.
5.4 추적-2 5.4.1 표준함수를 디버깅 할 때 디버깅은 F11로 한 라인씩 실행하고, Ctrl+F10 으로 커서 위치까지 실행 그러나 printf() 함수를 만나면 표준함수이기 때문에 건너 뛰어야 한다. 즉, 함수 내부로 디버깅하지 않고 바로 실행을 해야 한다. 따라서 처음 디버깅을 시작하면 아래 그림처럼 기본 기능 이외에 Step Over(F10) 라는 기능이 디버깅 창에 나타난다. 그림 5.11 함수 건너뛰기 기능(Step Over: F10)
그림 5.12 표준 함수를 만나는 경우 표준 함수를 만나면 F10 기능을 사용하면 함수 내부로 들어가지 않고 바로 실행하면서 함수를 건너뛴다. 따라서 표준 함수는 F10 키를 이용하여 건너뛰면 된다.
5.4.2 여러 개의 변수 값들을 보고 싶을 때 그림 5.13은 여러 개의 변수들이 존재하지만 디버깅이 진행되면서 전체 변수를 보여주는 것이 아니고 현재 진행 중인 변수들의 값만 보여준다. 따라서 이전의 변수 값들을 참고할 경우가 생기면 이때 활용하는 것이 그림 5.13의 Watch 창이다. Watch 창으로 변수들을 옮기면 Watch 창에 있는 변수들은 프로그램이 진행되는 모든 과정에서 변수들의 변하는 값들을 보여준다. 변수의 이동은 처음 변수들이 나타날 때 마우스를 이용하여 왼쪽의 변수들을 드래그(drag)하여 Watch 창으로 이동하면 된다.
그림 5.13 변수가 보이지 않을 때
그림 5.14 Watch 창을 이용한 변수 표현
디버깅할 때는 라인단위로 하거나, 커서의 위치까지 디버깅하거나, 표준함수를 건너뛰는 기능으로 디버깅한다. 또한 변수들의 값들이 변하는 과정을 참고할 때는 현재 진행 중인 변수들의 값은 알 수 있지만 이전의 변수 값들은 Watch 창을 이용하여 디버깅하면 보다 쉽게 프로그램 에러를 잡을 수 있다.