강좌 & 팁
지금껏 여러 가지 프로그램을 만들어 왔습니다만, 항상 작업 중에 신경이 쓰이는 것이 메모리 관리입니다. 전역 변수도 길가의 고깃 덩어리라고 생각하지만, 무엇보다도 malloc() 같은 메모리 할당 함수 사용은 항상 조심하게 됩니다. 나름 열씸히 free()함수와 짝을 맺게 해서 메로리가 누수되는 불행한 일이 없도록 노력합니다. 그러나 사람이 실수라는 것이 있는데, 아무리 잘못 없이 작성했다고 하더라도 아무 이상이 없는지 확인하는 것도 프로그램에 대한 신뢰를 올릴 수 있어 심적으로도 안심하고 부담감을 줄일 수 있습니다.
또, 나는 이상이 없지만 다른 사람의 코딩에 문제가 있을 수 있으므로 확인할 수 있다면 확인하는 것이 좋겠지요. 그렇다면 과연 어떤 방법이 있을까요? 책에서 보니 여러 가지 방법이 소개되어 있습니다만, 그중에 Valgrind 프로그램이 눈에 띄어 소개합니다.
Valgrind 설치
이 글을 작성하고 있을 때의 Valgrind는 3.5.0입니다. 아래으 링크를 방문해서 최신 버전을 내려 받습니다.
저는 리눅스에서 wget을 이용하여 직접 내려 받아 설치하는 것을 좋아합니다. 이런 유틸리티는 슈퍼유저로 설치하는 것이 좋겠지요.
]$ su - ]# cd /tmp
]# wget http://www.valgrind.org/downloads/valgrind-3.5.0.tar.bz2 ]# tar xvf valgrind-3.5.0.tar.bz2 ]# cd valgrind-3.5.0 ]# ./configure && make && make install
Valgrind를 이용한 테스트
Vagrind를 테스트하기 위해 문제가 있는 프로그램을 작성해 보겠습니다.
#include <stdio.h> #include <stdlib.h> #include <string.h> void memory_leak( void) { char *p_leak; int ndx; p_leak = malloc( 10); for ( ndx = 0; ndx < 10; ndx++) { p_leak[ndx] = 'a'; } for ( ndx = 0; ndx < 10; ndx++) { printf( "%c", p_leak[ndx]); } } int main( void) { memory_leak(); }
이제 컴파일하고 Valgrind로 문제가 있는 체크해 보겠습니다. 주의하실 것은 Valgrind는 root권한으로 실행하셔야 제대로 실행됩니다. 일반 유저로 실행하면 블록되더군요. 프로그램 소스는 test.c로 저장하고 -o 옵션을 주어 실행 파일 이름을 app_test라고 하겠습니다.
]# gcc test.c -o app_test
]# valgrind --leak-check=yes ./app_test
==8017== Memcheck, a memory error detector
==8017== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==8017== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==8017== Command: ./app_test
==8017==
aaaaaaaaaa==8017==
==8017== HEAP SUMMARY:
==8017== in use at exit: 10 bytes in 1 blocks
==8017== total heap usage: 1 allocs, 0 frees, 10 bytes allocated
==8017==
==8017== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==8017== at 0x4005903: malloc (vg_replace_malloc.c:195)
==8017== by 0x80483C5: memory_leak (in /home/jwjw/app_test)
==8017== by 0x8048423: main (in /home/jwjw/app_test)
==8017==
==8017== LEAK SUMMARY:
==8017== definitely lost: 10 bytes in 1 blocks
==8017== indirectly lost: 0 bytes in 0 blocks
==8017== possibly lost: 0 bytes in 0 blocks
==8017== still reachable: 0 bytes in 0 blocks
==8017== suppressed: 0 bytes in 0 blocks
==8017==
==8017== For counts of detected and suppressed errors, rerun with: -v
==8017== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 12 from 8)
]#
색을 달리하는 행을 보면 main() 함수에서 memory_leak() 함수를 호출했는데, 그 안에서 malloc()를 호출했는데, 이 부분에 문제 있다는 것을 제대로 걸러서 보여 줍니다. 그런데 함수 이름만 나오니까 좀 밋밋하지요? 행 번호까지 나오면 좋을 텐데. 컴파일 할 때 -g 옵션을 주면 됩니다.
]# gcc -g test.c -o app_test ]# valgrind --leak-check=yes ./app_test ==8025== Memcheck, a memory error detector ==8025== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al. ==8025== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info ==8025== Command: ./app_test ==8025== aaaaaaaaaa==8025== ==8025== HEAP SUMMARY: ==8025== in use at exit: 10 bytes in 1 blocks ==8025== total heap usage: 1 allocs, 0 frees, 10 bytes allocated ==8025== ==8025== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==8025== at 0x4005903: malloc (vg_replace_malloc.c:195) ==8025== by 0x80483C5: memory_leak (test.c:10) ==8025== by 0x8048423: main (test.c:24) ==8025== ==8025== LEAK SUMMARY: ==8025== definitely lost: 10 bytes in 1 blocks ==8025== indirectly lost: 0 bytes in 0 blocks ==8025== possibly lost: 0 bytes in 0 blocks ==8025== still reachable: 0 bytes in 0 blocks ==8025== suppressed: 0 bytes in 0 blocks ==8025== ==8025== For counts of detected and suppressed errors, rerun with: -v ==8025== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 12 from 8) ]#
하나 더 해 봅시다.
아래의 프로그램은 어디가 잘못 되었을까요?
001 #include <stdio.h> 002 #include <stdlib.h> 003 #include <string.h> 004 005 int main( void) 006 { 007 char *p_error; 008 int ndx; 009 010 p_error = malloc( 10); 011 012 for ( ndx = 0; ndx <= 10; ndx++) 013 { 014 p_error[ndx] = 'a'; 015 } 016 p_error[10] = '\0'; 017 puts( p_error); 018 free( p_error); 019 }
컴파일하고 실행해 보면 어떤 에러도 없이 실행이 잘됩니다.
]# gcc -g test.c -o app_test
]# ./app_test
aaaaaaaaaa
]#
벌써 소스를 보시고 문제점을 찾은 분도 계시겠습니다만, valgrind메 맡겨 보겠습니다.
]# valgrind --leak-check=yes ./app_test
==8036== Memcheck, a memory error detector
==8036== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==8036== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==8036== Command: ./app_test
==8036==
==8036== Invalid write of size 1
==8036== at 0x8048413: main (test.c:14)
==8036== Address 0x4019032 is 0 bytes after a block of size 10 alloc'd
==8036== at 0x4005903: malloc (vg_replace_malloc.c:195)
==8036== by 0x8048400: main (test.c:10)
==8036==
==8036== Invalid write of size 1
==8036== at 0x8048426: main (test.c:16)
==8036== Address 0x4019032 is 0 bytes after a block of size 10 alloc'd
==8036== at 0x4005903: malloc (vg_replace_malloc.c:195)
==8036== by 0x8048400: main (test.c:10)
==8036==
==8036== Invalid read of size 1
==8036== at 0x4006813: strlen (mc_replace_strmem.c:275)
==8036== by 0xC82EA4: puts (in /lib/libc-2.5.so)
==8036== by 0x8048433: main (test.c:17)
==8036== Address 0x4019032 is 0 bytes after a block of size 10 alloc'd
==8036== at 0x4005903: malloc (vg_replace_malloc.c:195)
==8036== by 0x8048400: main (test.c:10)
==8036==
aaaaaaaaaa
==8036==
==8036== HEAP SUMMARY:
==8036== in use at exit: 0 bytes in 0 blocks
==8036== total heap usage: 1 allocs, 1 frees, 10 bytes allocated
==8036==
==8036== All heap blocks were freed -- no leaks are possible
==8036==
==8036== For counts of detected and suppressed errors, rerun with: -v
==8036== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 12 from 8)
]#
자, 어떤 문제인지 아시겠습니까? valgrind가 언급한 행을 보면 14, 16, 17행입니다. 14, 16행 쓰기 크기에 문제가 있고, 17행은 읽을 크기가 유효하지 않다는 메시지입니다.
C에서 배열 첨자의 시작은 0부터 합니다. 그러므로 p_error은 p_error[0]부터 p_error[9]까지만 유효합니다. 그런데 14행은 for 루프에서 ; ndx <= 10; 구문으로 범위를 넘겼고 16행은 아예 범위를 벗어나서 지정했습니다. 17행은 문제가 있는 메모리를 참조하여 출력했습니다.
014 p_error[ndx] = 'a'; 016 p_error[10] = '\0'; 017 puts( p_error);
Valgrind로 메모리 누수를 막자
짦은 샘플을 보았습니다만, 매우 유용하겠지요? 한가지 아쉬운 점은 Valgrind에서 프로그램을 실행해야 하기 때문에 테스팅하는데 시간과 노력이 생각보다 많이 소모될 수 있습니다. 그래서 때로 모듈 별로 나누어 테스트를 해야 될지도 모르지만, 불편을 감수하고 확인한다면 튼튼한 프로그램을 만들 수 있을 뿐 아니라 심적 부담부터 줄일 수 있을 것입니다. 자주는 아니더라도 중요할 때마다 사용해야 겠습니다.