강좌 & 팁
최근에 전화를 받았는데 프로그램 실행이 이상하다고 합니다. 배열 변수를 사용하는데 평소에는 문자열이 잘 출력되다가도 어느 때는 이전 문자열이 겹쳐 출력된다는 것이었습니다. 전화로 통화하다 보니 자세히 알 수 없지만, 이전 문자열이 겹쳐 보일 때가 있다면 함수를 호출하기 전에 배열 변수를 초기화를 하지 않았거나 해당 변수가 메모리를 복사해 줄 때 뒤에 NULL 문자를 붙여 주지 않아서라고 설명해 주었습니다. 그래서 함수를 호출하기 전에 memset()으로 초기화를 해 주라고 했습니다. 이렇게 말이죠.
memset( p_buff, 0, sizeof( p_buff));
그런데 문제가 계속 발생한다고 하네요. 전화로만 통화를 하다 보니 이해를 하지 못했는데, 그 분의 소스를 받아 보고서야 이해를 했습니다. 메모리 사용을 알뜰하게 코딩하고 싶어서 배열의 크기를 너무 타이트하게 잡은 것이 문제였습니다.
#include <stdio.h> #include <string.h> static char buff_a[3]; static char buff_b[10]; void get_text( char *p_buff, char *p_str){ strcpy( p_buff, p_str); } int main( int argc, char *argv[]){ get_text( buff_a, "ab"); get_text( buff_b, "ab"); printf( "buff_a=%s buff_b=%s\n", buff_a, buff_b); get_text( buff_a, "abc"); get_text( buff_b, "abc"); printf( "buff_a=%s buff_b=%s\n", buff_a, buff_b); return 0; }
예를 들기 위해 buff_a 배열 크기를 [3]으로 잡았지만, 1024나 적당해 보이는 크기의 숫자를 넣으면 이런 실수를 하게 됩니다.
C 언어의 배열은 매우 편한 변수이지만, 잘못 사용하면 프로그램을 매우 불안하게 만듭니다.
요즘 컴파일러는 많이 좋아 져서 실행 중에 배열 변수를 그대로 사용한다면, 그리고 배열 크기를 넘어 값을 대입하면 실행 중에 버퍼 오버 에러를 출력하면서 멈추게 됩니다.
*** buffer overflow detected ***: ./app_test terminated
그러나 포인터로 받는 함수로 넘기면 그 함수에서는 배열의 크기를 알 수 없어 위와 같이 버퍼 체크를 하지 못합니다. 즉, 에러를 출력하지 못하고 아무런 문제가 없다는 듯 실행하게 되는데 이것은 매우 심각한 문제가 될 수 있습니다.
위의 예를 보면 첫 번째 get_text()로 buff_a는 첫 번째 바이트부터 [0:a][1:b][2:NULL]로 이상 없이 값을 받을 수 있습니다.
그러나 두 번째 get_text()에서는 buff_a에 첫 번째 바이트부터 [0:a][1:b][2:c]까지는 이상 없이 대입하지만, 문제는 NULL을 넣을 공간이 없습니다. 대신에 연이어 배열 공간인 buff_b[0]에 NULL을 넣게 됩니다.
buff_a | buff_b | |||||||||||
0 | 1 | 2 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
a | b | c | NULL |
이상태에서 buff_a를 printf()로 출력해도 이상 없이 "abc"가 출력됩니다. 문제는 buff_b를 출력하면 아무런 문자가 출력되지 않죠. 왜? get_text( buff_a, "abc") 로 엉뚱하게 buff_b[0]에 NULL이 들어 가니까요.
이 상태에서 다시 buff_b에 "abc"를 strcpy()를 하게 되면 결국 이렇게 됩니다.,
buff_a | buff_b | |||||||||||
0 | 1 | 2 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
a | b | c | a | b | c | NULL |
이렇게 되면 buff_a는 생각지도 않게 "abcabc"로 출력되고 buff_b는 정상적으로 "abc"가 되는 것이죠.
즉, buff_a는 대입되는 문자열의 크기에 따라 제대로 실행되었다가도 엉뚱하게 실행되게 됩니다.
이런 문제가 생기는 것을 미연에 막기 위해서는 크기 계산을 정확히 해야 하고 조금 넉넉하게 잡아야 합니다.