728x90

디버깅

프로그램을 작성하는 도중 오타를 입력하는 등의 실수를 해서 에러를 발생시킬 수 있습니다. 프로그램에서의 에러를 흔히 버그라 하고, 에러를 찾아 고치는 것을 디버깅이라 합니다.

예제 2.4는 에러가 몇 개 있는 프로그램입니다. 몇 개의 에러가 있을까요?

예제 2.4 - error.c 프로그램

/* error.c -- 에러가 몇 개 있는 프로그램
#include <stdio.h>

int main(void) (
    int n1, int n2, int n3;

    n1 = 10;
    n2 = n1 * n1;
    n3 = n2 * n2;

    printf("n1 : %d, n1의 제곱 : %d, n1의 세제곱 : %d", n1, n2, n3)

    return 0;
)

신택스 에러(Syntax Error)

예제 2.4는 신택스 에러를 가지고 있습니다. 신택스 에러는 C의 규칙을 따르지 않았을 때 발생하는데, 이것은 영어의 문법 오류와 비슷합니다. I Pretty be 이 문장은 유효한 단어들을 사용하고 있지만, 어순 규칙을 따르지 않고, 단어들 또한 부정확합니다. 이처럼 신택스 에러는 유효한 C의 기호를 잘못된 위치에 사용했을 때 주로 발생합니다.

error.c에는 어떤 신택스 에러가 있을까요? 먼저, 중괄호({}) 대신 괄호(())를 사용해서 함수의 몸체를 나타내고 있습니다.

두 번째로 변수의 선언이 다음과 같이 되어야 합니다.

int n1, n2, n3;

또는 다음과 같이 되어야 합니다.

int n1;
int n2;
int n3;

그다음에, 주석을 끝낼 때 필요한 */ 기호와 printf()문을 끝내는 세미콜론이 빠져있습니다.

신택스 에러는 어떻게 찾을 수 있을까요? 먼저, 컴파일하기 전에 에러가 있는지 확인하면서 소스 코드를 훑어볼 수 있습니다. 또는, 신택스 에러를 찾는 것도 컴파일러가 해야 하는 일 중 하나이므로, 컴파일러가 찾아낸 에러를 조사할 수 있습니다. 컴파일러는 컴파일할 때 에러가 발견된다면 에러의 종류와 위치를 함께 알려줍니다.

그러나 컴파일러라고 항상 맞는 것은 아닙니다. 어떤 위치에 있는 진짜 신택스 에러 하나를 가지고 또 다른 에러 발견되었다고 생각할 수도 있습니다. 예를 들면, 이 예제는 n2n3를 정확히 선언하지 않아서, 이 변수들이 사용되는 곳마다 에러가 발견되었다고 할 수 있습니다.

컴파일러가 보고하는 모든 에러를 이해할 수 없다면, 이들을 한꺼번에 고치려 시도하지 말고 처음 한두 개만 고친 후 다시 컴파일해야 합니다. 이렇게 하면 몇몇 가짜 에러들이 사라질 것입니다. 프로그램이 제대로 작동할 때까지 이 과정을 반복하면 됩니다. 또, 컴파일러는 간혹 한 라인 늦게 에러를 보고하기도 합니다. 예를 들면, 한 문장에서 세미콜론이 빠져있는 경우 다음 라인을 읽을 때까지 컴파일러는 세미콜론이 빠져있다고 생각하지 않습니다. 따라서 세미콜론이 분명히 붙어있는 라인에 대해서 세미콜론이 빠졌다고 불평할 수도 있습니다.

시맨틱 에러(Semantic Error)

시맨틱 에러는 의미상의 에러입니다. 예를 들면, James boast thinks green이라는 문장은 형용사, 명사, 부사가 모두 올바른 위치에 있어서 신택스는 틀리지 않았지만, 의미가 없는 문장입니다. 이처럼 규칙을 모두 따랐지만 잘못된 결과가 발생했을 때 시맨틱 에러라고 부릅니다. 앞의 예제에서 시맨틱 에러는 다음과 같은 경우입니다.

n3 = n2 * n2;

여기서 n3n1의 세제곱을 표현하려 했지만, 실행시켜보면 n1의 네제곱이 출력됩니다.

시맨틱 에러는 C의 규칙을 어긴 것이 아니라서 컴파일러가 찾아낼 수 없습니다. 프로그래머의 진짜 의도를 컴파일러는 모르기 때문에 시맨틱 에러를 찾는 것은 전적으로 프로그래머의 몫입니다. 이 에러를 찾는 유일한 방법은, 의도했던 결과와 프로그램의 실행결과를 비교해보는 것입니다. 예를 들어, 이 예제에 있는 신택스 에러를 고쳐서 이제 예제 2.5처럼 되었다고 가정합시다.

예제 2.5 - stillerror.c 프로그램

/* stillerror.c -- 신택스 에러를 수적한 프로그램 */
#include <stdio.h>

int main(void) {
    int n1, n2, n3;

    n1 = 10;
    n2 = n1 * n1;
    n3 = n2 * n2;

    printf("n1 : %d, n1의 제곱 : %d, n1의 세제곱 : %d", n1, n2, n3);

    return 0;
}

프로그램의 실행 결과는 다음과 같습니다.

n1 : 10, n1의 제곱 : 100, n1의 세제곱 : 10000

10의 세제곱은 10000이 아닙니다. 이제 다음 단계는 어떻게 이런 결과가 나왔는지 되짚어보는 것입니다. 이 예제의 경우에는 자세히 들여다보면 에러를 쉽게 찾을 수 있겠지만, 좀 더 체계적으로 접근할 필요가 있습니다. 그 방법은 스스로가 컴퓨터가 되었다고 생각하고 프로그램의 단계들을 순차적으로 하나씩 밟는 것입니다. 이 방법을 한 번 시도해봅시다.

프로그램은 3개의 변수 n1, n2, n3를 선언하면서 시작합니다. 상자 세 개를 그려놓고 거기에 변수 이름을 하나씩 써 붙여 이 상황을 시뮬레이션해볼 수 있습니다. 먼저, 프로그램은 n110을 대입합니다. n1 상자에 10을 써넣은 다음, 프로그램이 n1 * n1을 해서 그 결과를 n2에 대입하므로 n2 상자에 n1 * n1의 결괏값 100을 써넣으세요. 그다음에는 n3 = n2 * n2;을 시뮬레이션하면서 n2 * n2의 결괏값 10000n3 상자에 쓰면 됩니다. 여기서 잘못된 부분을 알아낼 수 있는데, n2 * n2가 아닌 n2 * n1(100 * 10)이라 적어야 합니다.

이 절차를 위 예제에 적용하는 것은 지나치다는 감이 있지만 이런 식으로 프로그램을 한 단계씩 추적하는 것은 프로그램에서 무슨 일이 일어나는지 알 수 있는 최선의 방법입니다.

프로그램의 상태

프로그램의 각 단계에 따른 변수를 확인하면서 프로그래머는 프로그램의 상태를 확인합니다. 프로그램의 상태는 프로그램이 실행되고 있는 시점에서 모든 변수가 가지고 있는 값의 집합을 말합니다.

지금까지 머릿속으로 프로그램을 실행시켜 프로그램의 상태를 추적하는 방법에 관해이야기했습니다. 작업을 수천 번 하는 프로그램이라면, 위와 같은 방법은 사용하기 힘들 것입니다. 그래서 이 방법 외에도 프로그램의 상태를 확인할 수 있는 다른 방법들을 소개하려 합니다.

프로그램의 상태를 확인하는 또 다른 방법은, 선택된 변수들의 값을 출력하는 printf()문을 프로그램의 중요 지점에 작성하는 것입니다. 이렇게 하면 값들이 변하는 것을 보고, 프로그램에서 어떤 일이 일어나는지 유추할 수 있습니다. 프로그램에 문제가 없다면 printf()문들을 제거한 뒤, 다시 컴파일하면 됩니다.

프로그램의 상태를 확인하는 세 번째 방법은 디버거를 사용하는 것입니다. 디버거는 프로그램을 단계별로 실행시키면서 그 프로그램의 변숫값들을 확인할 수 있게 해주는 프로그램입니다. 디버거는 사용 용이성과 복잡도에 따라 종류가 많은데, 몇몇 디버거는 어느 라인이 실행되고 있는지 보여주기도 합니다. 이것을 이용하면 복잡한 프로그램을 디버깅할 때 매우 편리합니다. 컴파일러가 디버거를 제공한다면 예제 2.4를 디버깅해보면서 디버거의 사용법을 배워두는 것이 좋습니다.

728x90
728x90