written by 김시찬(chanywa), 2004-05-05

논리적 오류는 며느리도 몰라

컴파일과 링크단계에서 출력되어지는 에러메세지는 해결하기가 쉽다. 어디서 뭐가 틀렸는지 눈으로 보이기 때문이다. 하지만 정말 프로그래머들의 진을 빠지게 하는 것은 컴파일과 링크가 잘 되고 실행도 되면서도 정상적으로 동작하지 않게 하는 논리적 오류인 것이다.
필자도 항상 이런 것들 때문에 헤매는 경우가 많다. 한참을 고민하고 노력한 끝에 찾은 원인들은 너무나 보잘것 없는 것들이 대부분이다. 이러한 것들을 글로 남겨서 다음에 유사한 증상이 발생했을 때 나는 물론이고 다른 사람들도 해결하는데 도움이 되었으면 한다.


Q1 : 문자열 출력시 깨진 문자가 출력된다


A1-1 : 주로 문자열이 저장될 메모리 공간을 제대로 확보 못했을 경우에 발생한다.

배열이나 동적 메모리할당을 사용했을 때, 문자열 끝에 꼭 들어가야하는 NULL('\0') 문자가 들어갈 공간이 없을 경우에 발생할 수 있다. 예를 들어,

char string[10];
strcpy( string, "가나다라마" );

의 경우처럼 10 byte 공간에 문자가 10 byte 들어가게 되면 NULL이 들어갈 공간이 없다. 이때는 경우에 따라 메모리상의 string 변수 끝에 NULL이 들어갈 수 있기 때문에 정상출력이 되기도 한다. 하지만 다른 변수들을 사용하거나 할 경우, NULL이 있는 공간은 컴퓨터가 생각하기에 사용하지 않는 영역이라 생각하고 다른 데이터를 기록할 수 있는데, 이럴 때 문제가 발생한다.

A1-2 : 혹은 아예 포인터에다가 문자열을 복사하게 되는 경우도 있다.

char *string;
strcpy( string, "가나다라마" );

물론 이 경우도 정상적으로 동작할 때도 있다. 하지만 대부분 오류를 일으킨다. 여기서 string은 문자열 자체를 보관하는 변수가 아니라, 문자열의 시작위치를 저장한 주소값 변수(포인터)에 지나지 않는다. 그렇기 때문에 그곳에 문자열을 복사하더라도 메모리공간이 확보되지 않았기 때문에 데이터가 제대로 유지되지 않는다.


Q2 : 무한루프에 빠진다

A2-1 : 무한루프에 빠질 경우에는 루프를 돌게 되는 조건문을 살펴보라.

while(), for() 문등을 사용할 때에는 루프를 계속적으로 돌기위한 조건을 넣는다. 이때의 조건문에서 논리적 오류를 일으켜 계속적으로 순환되도록 하여 무한루프가 발생한다.
포인터 변수를 조건문내에 사용할 경우, 포인터가 가리키는 곳의 값이 아니라 포인터 변수값 자체를 사용할 때 발생할 수 있다.

int a = 5, *p = a;
while( p )
{
    a--;
}

이 소스를 작성한 의도는, p가 가리키고 있는 a변수의 값이 0가 되면 while()문을 빠져나오도록 한 것이다. 하지만 포인터 변수 p를 일반 변수로 착각하여 사용하였을 때는 무한루프에 빠질 수 있다. 왜냐하면 p는 항상 a 변수의 주소값이기 때문에 0이 될 수 없다. 이때는 p를 *p로 수정해야 한다.

A2-2 : 또는 조건문 내에 비교문을 잘못 사용하여 대입연산자를 사용했을 때 발생할 수 있다.

int a = 5, b = 10;
while( a = b )
{
    b--;
}

이 프로그램을 작성한 사람의 의도는 b를 계속적으로 1씩 감소시켜서 a와 b가 같아질 경우에 루프를 빠져나오는 것이다. 그러나 비교연산자 '==' 대신에 실수로 대입연산자인 '='를 사용함으로써 무한루프에 빠지게 되는 것이다.
이때 a = b 문장은 항상 TRUE 값을 반환하게 된다.


Q3 : 연산이 제대로 안되어 엉뚱한 값이 된다

A3-1 : 가장 쉽게 생각할 수 있는 부분은 변수의 표현범위 초과로 인한 문제이다.

int sum1 = 40000, sum2 = 40000, mymoney = 30000;
sum1 += mymoney + 10000;
sum2 = sum2 + mymoney + 10000;

과연 sum1과 sum2는 얼마일까??? 그냥 보기에는 40000 + 30000 + 10000 = 800000 으로 보이지만, 이것은 여러가지 답으로 나올 수 있다.

첫째로 따져야 할 것은 컴파일러이다. 같은 컴퓨터라 할 지라도 컴파일러에 따라서 한 개의 word 단위가 다르다. 구버전 Turbo C/C++ 같은 경우에는 1 word = 2 bytes 이다. Visual C++ 등의 경우에는 32bit 컴파일러이므로 1 word = 4 bytes 이다.
즉, sum1의 경우 mymoney + 10000이란 값은 40000이 될 수도 있고, -7232(맞나?)가 될 수도 있다.

둘째는 연산순서이다. sum1이나 sum2나 똑같아 보일 수 있다. 물론 다르다고 생각하는 공부 열심히한 착한 학생들도 있겠지만, 언제나 실수는 할 수 있는 법이다. sum1의 경우에는 sum1 = sum1 + ( mymoney + 10000 )가 된다. 하지만 sum2의 경우에는 sum2 = ( sum2 + mymoney ) + 10000이 된다. 연산순서가 다르게 되면 변수의 표현범위를 초과하지 않을 수도 있기에 같은 값이 나올 수도 있지만, 엄연히 계산 순서는 틀리기 때문에 다른 값이 저장될 수도 있다.


A3-2 : 비슷한 경우지만 변수 타입을 제대로 고려하지 않은 연산시 혹은 그 연산값을 비교하는 경우에 발생하는 수도 있다.

unsigned int mymoney = 3000, yourmoney = 2000;

if( mymoney - 4000 < yourmoney )
    printf("내 돈이 당신 돈 보다 4000원 이상 많지는 않다.");
else
    printf("내 돈이 당신 돈 보다 4000원 이상 많다.");

한 사람이 가질 수 있는 돈의 최대액수가 50000원이라고 하자. int형은 50000이란 표현이 불가능하므로 대신에 unsigned int를 사용했다. 그리고 내 돈이 상대방 돈보다 4000원 이상 많은지 아닌지를 판단하는 루틴을 작성하였다. 결과값은 무엇일까?

이론상으로 계산했을 때는 당연히 4000원 이상 많지 않다라고 나와야 한다. 하지만 비교문에서는 변수타입에 대한 고려를 빠뜨리는 경우가 많다. 그래서 ( mymoney - 4000 )은 -1000이 아니라 64535 값이 되면서 4000원 이상 많다라고 판단하게 되는 것이다.

이런 경우에는 (-) 연산 대신에 (+) 연산을 사용하도록 하자.
즉, if( mymoney < yourmoney + 4000 ) 과 같은 식으로 말이다. <계속...?>

'develop' 카테고리의 다른 글

C & C++ 무료 컴파일러  (2) 2009.09.24
#include를 조심하라  (0) 2004.07.28
논리적 오류 해결하기  (0) 2004.05.05
독학인가, 강습인가  (0) 2004.04.27
for문 날씬하게 사용하자  (2) 2004.04.19
어떤 언어를 선택해야 하나  (2) 2004.01.08
Posted by 찬이

댓글을 달아 주세요