2014년 11월 16일 일요일

디버그 방법

C가 보이는 그림책에서 발췌

사진 출처 : https://www.google.co.kr/url?sa=i&source=web&cd=&ved=0CAUQjB0&url=http%3A%2F%2Fwind8apps.com%2Fdebugging-tools-windows-8-1%2F&ei=VZdqVPXSLePEmwXr3oG4BQ&psig=AFQjCNFIunpNz4D0Go1OqTN18Lm8BzMsQA&ust=1416358053469916

프로그램에는 버그(오류)가 따르기 마련입니다. 아무리 우수한 프로그래머라도 한 번에 버그 없는 프로그램을 작성할 수는 없습니다. 프로그래머가 생각하단 대로 동작하지 않을 때는 버그를 찾아 수정하는 [디버그]라는 작업을 하게 됩니다.

에러의 종류

프로그래밍을 하면서 맨 처음 부딪히는 벽은 컴파일 에러입니다. 프로그램을 컴파일할 수 없는 원인으로는 문법상의 오류(철자법 에러)이거나, 컴파일 방법이 틀렸다거나 여러가지 원인이 있을 수 있겠지만, 컴파일러는 어느 부분에서 잘못되엇는지 지적해 주지 않습니다.

C 언엉의 컴파일러가 출력하는 에러 메세지는 마치 컴베이어 위에서 이루어지는 작업 같아서, 한 군데에 에러가 있더라도 그로 인하여 그 이하의 문장들이 꼭 들어맞지 않으면 그 때마다 메세지를 표시해 버립니다. 에러 메세지가 너무 많이 표시될 때는 당황하지 말고 어떤 메세지가 본질적인 원인가 자세히 보고 수정할 필요가 있습니다.
그런데 컴파일을 무사히 마쳤다고 해서 제대로 프로그램을 만들어졌다고 할 수는 없습니다. 가장 디버그하기 힘든 것이 프로그램 실행 중에 일어나는 에러입니다. 버그라고 하면 보통 이것을 지칭합니다. 프로그램이 멈춰버리거나(런타임 에러), 멈추진 않지만 프로그램 동작이 이상하다거나, 생각대로 동작은 하지만 틀린 결과가 나오는 등 여러 종류가 있습니다.

예를 들어[i = 3;]은 변수 i에 3을 대입하는 문인데, 이 문의 부호 =을 실수로 두개를 써 버리게 되면 [i==3]으로 i와 3이 같은지를 빅 ㅛ하는 식이 됩니다. 비교를 행하는 식만 달랑 서 놓은 것은 아무런 의미가 없지만, 문법적으로는 올바르기 때문에 컴파일러는 에러를 표시하지 않습니다. 결국, 의도하고 있던 [i에 3을 대입하는] 처리가 수행되지 않는 프로그램이 만들어지는 것입니다.

버그의 발견

프로그램의 버그를 발견하려면,  우선 소스 프로그램을 차분히 읽는 것이 기본입니다. 자신이 생각했던 처리가 생각대로 기술되어 있는지 한 번 더 확인해 봅니다. 그래도 알수 없을 경우는 프로그램이 어떻게 동작하고 있는지를 자세히 조사해 볼 필요가 잇습니다. 이제부터는 디버그 할 때 자주 사용된는 몇 가지 기법에 대해 그 목적과 방법을 설명하겠습니다.

처리를 분할한다

C 언어에서는 식이나 문의 기술이 매우 유연하여, 생각하기에 따라 한번에 모아서 작성 할 수도 있지만, 버그의 온상이 되기도 합니다. 만약 한데 모아서 작성했다면, 처리나 의미 단위로 분ㄴ할하도록 합시다. 런타임 에러가 표시하는 정보는 [몇 번째 줄에 얼가 발생했습니다]라는 내용이 많기 때문에, 한 줄에 여러개의 문을 기술하지 않는 편이 에러의 위치를 쉽게 파악할 수 있습니다.

연산자의 우선순윋도 익숙해지지 않으면 틀리기 쉬운 부분입니다. 식의 의미를 알기 힘들거나, 우선 순위가 확실치 않은 때에는 괄호를 사용한다거나, 한 번 변수에 대입해 두면 의미가 확실해지고 코드를 읽기 쉬어집니다. 특히 포인터나 배열에 관한 연산, 증가 연산 등을 많이 사용하면 소스 코드가 복잡해지기 때문에 주의하여 주십시오.

복잡한 처리도 이런 식으로 나누다 보면, 어느새 문제 부분이 드러나기 마련입니다.

printf()를 삽입한다.

컴파일한 프로그램을 그냥 실행시키게 되면 버그가 잇다는 것은 알 수 있어도 원인까지는 좀처럼 파익하기 힘듭니다. 그래서 소스 프로그램에 디버그용으로, 원래는 불필요한 pritnf()를 삽입하여 단서를 얻을 수 있습니다. 예를 들어, 프로그램 속에 [printf("이곳은 실행되었습니다.\n");]과 같이 써두면, 그곳에 도달햇을 때 메세지가 출력되어, 그 부분이 언제 실행되는 알 수 있는 것입니다.게다가 변수의 값을 표시하다록 해두면, 그 시점에서의 변수 값도 조사할 수 있습니다.

또한, 화면에 출력 내용이나 타이밍에 민간함 애플리케이션에서는 로그를 파일로 남기는 것도 효과적인 방법입니다. 이 때, 텍스트 추가모드("a")로 파일을 열어서 fprintf()로 로그를 추가해 갑니다.

pritnf()나 fpritnf()로 로그를 추가해 갑니다.

printf()나 fprintf()로 변수의 내용을 표시할 때는 서식 지정과 변수 형의 관계에 주의를 하십시오. int형 변수 a를 가지고 [printf("%s", a);]처럼 쓴다면, 오히려 새로운 버그를 만들게 됩니다.


함수별로 실행한다

C 언어에서의 처리 단위는 함수이기 때문에 함수를 테스트하는 일이 많을 것입니다. 함수에 다양한 인수를 부여하여 반환값을 조사하면, 그 함수가 정상적으로 동작하는 지 알 수 있습니다. 함수는 당양한 상황에서 호출되는데, main() 함수를 바꿔 써서 대상 함수를 바로 실행시킬 수 있도록 하는 것이 효율적인 방법입니다. main()함수를 일시적으로 치환하여 테스트하기 힘들 경우는 별도의 테스트용 프로그램을 만들어, 그 곳에서 테스트하고 싶은 함수를 호출하는 방법도 좋습니다.

처리의 흐름을 제어한다

버그가 잠재해 있는 곳을 찾을 때 조건 분기가 방해될 경우가 있습니다. 조건 분기는 상황에 따라 동작이 ㅏ뀌기 때문에 버그를 파악하기 힘들게 합니다. 이런 경우에는 조건 분기의 조건식을 잠시 바꿔, 직접 1(true) 나 0(false)를 써주는 것도 하나의 방법입니다. 이 방법은 에러 체크 등 그다지 실행되지 않는 부분을 테스트하기 위해서도 효과적입니다.

데이터의 구조를 추측한다.

때론 손만 움직이는 것이 아니라 추리력이 필요할 경우도 잇습니다. 복잡한 알고리즘이나 데이터 구조를 가진 프로그램들은 버그의 위치와는 전혀 관계없는 곳에서 이상한 동작을 하는 경우도 있습니다. 그런 때는 소스 프로그램을 몇 번이고 다시 읽어봐도 [이런 일이 일어날 리 없다]라는 결론밖에 도달하지 않습니다.

그러니, 머리를 써서 [이 부분의 메모리는 어떻게 사용되는 것 일까]라고 생각해 봅니다. 스스로 추리를 정리하기 위해 데이터 구조를 종이에 적어 보는 것도 좋습니다. 실제로. 배열의 범위 밖의 요소를 참조하거나 포인터가 잘못된 곳을 가리키고 있어 에러가 발생하는 경우가 많습니다. 이 같은 버그는 발견하기 힘들지만, 어느 정도 수준 있는 프로그램 에러 중에서는 흔한 것입니다.

디버거의 이용

지금까지는 소스 프로그램을 변경하여 디버그하는 방법을 소개하였습니다. 이것은 이른바 자력으로 버글르 발견하는 방법입니다. 그러나 프로그램의 규모가 커지게 되면 버그의 발견이나 소스 코드를 바꿔 쓰는 일 자체가 큰일이 됩니다.

그래서 툴의 힘을 빌리기로 하겠습니다. 디버그를 지원하는 툴을 디버거라고 하며, 컴파일러와 나란히 프로그램을 개발하는 데 있어 빠트릴 수 없는 것입니다. 대개의 개발 환경은 컴파일러와 디버거를 모두 가지고 있습니다. 대표적인 디버거의 기능으로는 아래와 같은 것들이 있습니다.

브레이크 포인트 설정

디버거를 사용하면 소스 프로그램에 지정한 위치에서 프로그램을 일시 정지할 수 있습니다. 브레이크 포인트라는 것은 그 위치를 지정하는 것입니다. 브레이크  포인트를 설정하고 프로그램을 실행하면, 설정된 위치에 도달햇을 때 프로그램이 일시 정지합니다.

변수의 표시와 변경

프로그램을 일시 정지했을 때, 그 때의 변수값을 참조할 수가 있습니다. 디버깅 방법 중 printf()를 사입하는 것과 같은 기능인데, 디버거를 이용하면 소스 프로그램을 변경하지 않고도 우너하는 변수값을 표시할 수 있습니다. 또한 많은 디버거에서는 변수의 값을 직접 치환하여 그 값으로 프로그램을 계속 실행시킬 수 있도록 되어 있습니다.

스텝실행

소스 프로그램을 한 줄씩 실행하는 기능입니다. 이 기능을 사용하면 프로그램이 실제 어떻게 동작하고 있는지, 어떤 시점에서 멈춰저리는지 파악할 수 잇습니다. 변수의 값을 표시해 두면, 변수 값의 변화를 조사할 수도 잇씁니다. 조건 분기가 많은 프로그램 등에 특히 편리한 기능입니다.

이렇게 디버거를 사용하면 소스 프로그램을 변경하는 것보다 훨씬 간단하고 효율적으로 디버그할 수 있습니다. 디버거를 사용할 수 있는 환경이라면 적극적으로 디버거를 사용하여 노력과 시간을 절약하도록 합시다.