malloc() 후에 free()가 안됩니다. 세그먼테이션 폴트

keedi의 이미지

안녕하세요.

이틀을 고민하고 검색을 했으나 원하는 답을 얻지 못해 힘들게 질문드립니다.
도움 부탁드립니다. 정말 밥이 목구멍으로 넘어가지 않습니다 ㅠㅠ

이중 포인터라 지저분하지만 한번 봐주시겠습니까?

1. 제가 메인에서 포인터를 선언하고,
2. 첫번째 함수에서 파라미터로 넘겨 malloc을 사용하여 메모리를 할당합니다.
3. 그리고 두번째 함수에서 또 포인터를 파리미터로 받아서 free를 하게 됩니다.

그런데 문제는 free가 되지 않고 정확하게 이곳에서 세그먼테이션 폴트가
뜨는군요. gdb에 의하면 mallopt에서 문제가 발생했다는 메시지를 남깁니다.

일단 문제의 free만 주석 처리를 하고 프로그램을 컴파일 하면 문제가 없고
동작도 잘 합니다. 하지만 저 위치 아니면 free를 해줄 수 가 없을 것
같아서 걱정입니다.

소스코드 전부를 올리면 보기 힘드실 것 같아서 메인과 malloc와 free하는
부분만 올리겠습니다. 원본 파일은 첨부하겠습니다.

k&r 2판의 예제 1-20을 풀다가 문제와는 상관없이 생기는 오류때문에
난관에 봉착했습니다. ㅠㅠ (문제 자체는 간단합니다...탭을 스페이스로 치환)

지저분한 소스지만 부탁드립니다. 결정적으로 왜 저 위치에서 free(*str)을
했을경우 세그폴트가 뜨는지 정말 알고 싶습니다.

우선 메인입니다. 문제가 발생하는 free(*str)은 dtab()함수에 존재합니다.

int main(int argc, char *argv[])
{
	char *result;

	if ( !check_arg(argc, argv) )
		exit(EXIT_FAILURE);

	if ( !read_file(argv[1], &result) )
		exit(EXIT_FAILURE);

	if ( !dtab(&result) )
		exit(EXIT_FAILURE);

	if ( !write_file(argv[1], result) )
		exit(EXIT_FAILURE);

	free(result);
	
	return 0;
}

메모리 할당해주는 부분입니다. (여기는 문제가 없네요.)

/*****************************************************************
 *
 * Function Name: read_file
 *
 * 	Input	: const char *filename, char **str 
 * 	Output	: int 1(success), 0(failure); 
 *
 * 	Comment	: 텍스트 파일의 내용을 문자열 배열에 덤프한다.
 * 		문자열 배열 포인터를 받아와서 그 값을 기록한다.
 * 		문자열 배열은 이 함수 내에서 메모리 할당이되며
 * 		이 문자열 배열을 사용하고 난 뒤에는 해제되어야만
 * 		한다. 파라미터로 오는 변수는 값이 할당되지 않은
 * 		순수 포인터여야한다.(배열은 안됨!)
 *
 *****************************************************************/
int
read_file(const char *filename, char **str)
{
	int n_cnt;
	char buf[MAXLINE];
	FILE *fp;

	if ( (fp = fopen(filename, "r")) == NULL ) {
		fprintf(stderr, "[read_file: %d] Can't open file: %s\n",
				__LINE__, filename);
		return 0;
	}

	/* 파일 내용 복사를 위한 여유 공간 마련 */
	n_cnt = 0;
	while ( fgets(buf, MAXLINE, fp) != NULL )
		n_cnt += strlen(buf);	
	*str = (char *)malloc(sizeof(char) * (n_cnt + 1));
	if ( *str == NULL ) {
		fprintf(stderr, "[read_file: %d] Mem alloc err\n",
				__LINE__);
		return 0;
	}

	/* 파일 포인터 초기 위치로 */
	if ( fseek(fp, 0, SEEK_SET) != 0 ) {
		fprintf(stderr, "[read_file: %d] file pointer seek err\n",
				__LINE__);
		return 0;
	}

	/* dump!! */
	while ( fgets(buf, MAXLINE, fp) != NULL )
		strcat(*str, buf);

	return 1;
}

이곳이 바로 문제가 생기는 부분입니다!
정확하게 free(*str)에서 문제가 생깁니다.

/*****************************************************************
 *
 * Function Name: dtab
 *
 * 	Input	: char **str
 * 	Output	: int 1(success), 0(failure)
 *
 * 	Comment	: file의 내용을 모두 덤프시킨 문자배열 포인터를
 * 		받아와서(str) 탭문자를 스페이스로 변환시킨다.
 * 		단 탭의 위치에 해당하는 만큼의 스페이스 개수로
 * 		치환한다.
 * 		(*str)[i] 표현에 주의할것,
 * 		str[i] 는 원하지 않는 결과 출력시킴
 *
 *****************************************************************/
int
dtab(char **str)
{
	int i, j, k, l;
	int n_tab, n_sp;
	char *new_str;

	/* tab 개수 체크 */
	n_tab = 0;
	for ( i = 0; *str[i] != '\0'; ++i )
		if ( *str[i] == '\t' )
			++n_tab;

	/* 파라미터 복사본 - 약간의 메모리 낭비 허용 */
	new_str = (char *)malloc(sizeof(char) *
			(strlen(*str) + (n_tab - 1) * TABSIZE + 1));
	if ( new_str == NULL ) {
		fprintf(stderr, "[dtab: %d] malloc failure\n", __LINE__);
		return 0;
	}

	/* tab -> space!! */
	for ( i = 0, j = 0; (*str)[i] != '\0'; ++i ) {
		new_str[j] = (*str)[i];
		if ( new_str[j] == '\n' ) {
			k = 0;
			++j;
		} else if ( new_str[j] == '\t' ) {
			n_sp = TABSIZE - (k % TABSIZE);
			for ( l = 0; l < n_sp; ++l )
				new_str[j++] = ' ';
		} else {
			++k;
			++j;
		}
	}
	new_str[j] = '\0';

	/* 원본 해제 후 복사본을 연결 */
	/* 문제가 있는 부분 */
	free(*str); /* 이부분이 문제입니다. */
	*str = new_str;

	return 1;
}
File attachments: 
첨부파일 크기
파일 ex0120.c5.81 KB
파일 a.c1.53 KB
keedi의 이미지

데비안 리눅스 2.4.28
gcc 3.3.5

환경에서 수행했습니다.

----
use perl;

Keedi Kim

yoocj9의 이미지

Quote:
* (*str)[i] 표현에 주의할것,
* str[i] 는 원하지 않는 결과 출력시킴

*str[i] 와 (*str)[i]는 의미가 다르겠네요.

Quote:
for ( i = 0; *str[i] != '\0'; ++i )
if ( *str[i] == '\t' )

이 부분을 수정해야겠습니다.

익명 사용자의 이미지

일단 malloc후의 *str에 부여된 메모리 주소랑 free하기 직전의 *str에 부여된 주소랑 비교해서 malloc후의 부여된 메모리 주소와 틀릴때에는 분명 malloc후free하기 전까지 어디선가 값이 틀리게 바뀌어 버렸을 가능성이 크구요..

-g 옵션 주시고 컴파일 하신후에 gdb 로 디버깅 하시면 훨씬 문제점을 발견하시기 쉽습니다.

hanzo69의 이미지

우선 read_file() 에서부터 문제가 있군요.
read_file() 끝부분에

   /*********************
       우선 버퍼의 첫 바이트를 0으로 만들어주세요.
   **********************/
   (*str)[0] = 0; /*  */

   /* dump!! */ 
   while ( fgets(buf, MAXLINE, fp) != NULL ) 
      strcat(*str, buf); 

   return 1; 

strcat 함수는 문자열의 끝에 추가해줍니다. 즉 대상버퍼에서 0이란 한 바이트를 찾아 그 위치부터 추가하죠.
그런데 *str 이 가리키는 버퍼엔 끝이 어딘지 모르므로 아마 엉뚱한 위치에 문자열들이 추가되었을 가능성이 있네요. 그게 아마 동적할당 메모리 블럭 구조를 망가뜨린게 원인이 아닐까 합니다.

일단 나머진 좀 더 봐야겠네요.

님ㅎ 즐~

sangwoo의 이미지

hanzo69 wrote:
우선 read_file() 에서부터 문제가 있군요.
read_file() 끝부분에
   /*********************
       우선 버퍼의 첫 바이트를 0으로 만들어주세요.
   **********************/
   (*str)[0] = 0; /*  */

   /* dump!! */ 
   while ( fgets(buf, MAXLINE, fp) != NULL ) 
      strcat(*str, buf); 

   return 1; 

strcat 함수는 문자열의 끝에 추가해줍니다. 즉 대상버퍼에서 0이란 한 바이트를 찾아 그 위치부터 추가하죠.
그런데 *str 이 가리키는 버퍼엔 끝이 어딘지 모르므로 아마 엉뚱한 위치에 문자열들이 추가되었을 가능성이 있네요. 그게 아마 동적할당 메모리 블럭 구조를 망가뜨린게 원인이 아닐까 합니다.

일단 나머진 좀 더 봐야겠네요.

아닙니다. 처음부터 추가합니다.

----
Let's shut up and code.

서지훈의 이미지

read_file()함수와 dtab()함수에서의 *str주소값이 같은지를 먼저 확인을 해봐야 할것 같군요.
제가 볼땐 별다른 조작이 없어서 변조 된 것은 없을거 같은데...?
가끔 프로그래밍 하다 보면은 이해가 안되는 일도 있기는 하죠.

<어떠한 역경에도 굴하지 않는 '하양 지훈'>

#include <com.h> <C2H5OH.h> <woman.h>
do { if (com) hacking(); if (money) drinking(); if (women) loving(); } while (1);

sangwoo의 이미지

문제는 free()루틴 이전의 malloc()루틴입니다.
dtab() 함수에서

   new_str = (char *)malloc(sizeof(char) *
         (strlen(*str) + (n_tab - 1) * TABSIZE + 1));

이 부분은, 원하시는 결과를 위해서는 다음과 같이 되어야겠죠.
   new_str = (char *)malloc(sizeof(char) *
         (strlen(*str) + n_tab * (TABSIZE - 1) + 1));

저 오류 때문에, n_tab의 개수에 따라서 malloc이 실패할 가능성이
있습니다. (간단히 생각해서 n_tab = 0일 경우, 음수가 될 수도 있죠.)
여기를 수정해주시면 문제 없이 작동할 것 같습니다.

----
Let's shut up and code.

sangwoo의 이미지

조금 긴 파일을 돌려봤더니 에러가 나네요.
dtab() 함수 안의
/* tab -> space!! */
부분을 어떻게 생각하시고 코딩하신건지 설명 부탁드리겠습니다.
k도 초기화되지 않았군요.

----
Let's shut up and code.

hanzo69의 이미지

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char sz1[4] = { 'a', 'b', 'c', 'd' };
char sz2[16] = { 'x', 'y', 'z', 0 };
char sz3[4] = "123";

int main()
{
   printf( "before strcat(sz1, \"123\")\n" );
   printf( "sz1 = %s\n", sz1 );
   printf( "sz2 = %s\n", sz2 );

   strcat( sz1, sz3 );
   printf( "after strcat(sz1, \"123\")\n" );
   printf( "sz1 = %s\n", sz1 );
   printf( "sz2 = %s\n", sz2 );
}

출력 결과
before strcat(sz1, "123")
sz1 = abcdxyz
sz2 = xyz
after strcat(sz1, "123")
sz1 = abcdxyz123
sz2 = xyz123

strcat 함수는 목적지 주소로부터 0을 찾은 다음 그 위치에서부터 추가합니다.
처음이 아닙니다.

님ㅎ 즐~

doldori의 이미지

연습문제 때문에 밥이 목구멍에 넘어가지 않으신다니 무척 열심이시군요. ^^
그래서 그런지 많은 분들이 도와주시나 봅니다. 저도 한마디 거들겠습니다.

dtab.c:

    /* tab -> space!! */
    k = 0;    // 추가
    for ( i = 0, j = 0; (*str)[i] != '\0'; ++i ) {

첫라인이 '\n'으로 시작하지 않으면 k가 초기화되지 않은 상태로 되니까요.

1장 연습문제를 하시면서 malloc에 저수준 파일 입출력까지... ^^;
이미 상당히 알고 계시는 분이군요. 그런데 마지막장을 제외한 TCPL의 모든 연습문제는
표준 라이브러리만 갖고도 풀 수 있는 것들입니다. 처음에는 그렇게 시도하신 것
같은데 실패하셨나 보군요. write_file()의 fp는 int가 아니라 FILE*이어야 합니다.

하드를 뒤져보니 예전에 제가 풀었던 코드가 있군요. 참고하시라고 올려봅니다.
(생각해 보니 공부하면서 풀었던 연습문제 코드를 코드 놀이터 게시판에 올리는
것도 재미있겠는데요.) 지금 같으면 getline() 대신 fgets()를 썼겠지만 책에 나온
코드를 그대로 쓰느라고 그랬던 것 같습니다.

댓글 첨부 파일: 
첨부파일 크기
파일 0바이트
sangwoo의 이미지

hanzo69 wrote:

strcat 함수는 목적지 주소로부터 0을 찾은 다음 그 위치에서부터 추가합니다.
처음이 아닙니다.

헉! 죄송합니다. 제가 잘못 알았군요.. :-) 생각해보니 처음부터라면
마지막 줄만 기록이 되겠군요. :oops: 정정해 주셔서 감사합니다.

그건 그렇고, 문제점 하나를 또 찾았습니다. 이게 치명적이었던 거 같습니다. :twisted: dtab()에서

   /* tab 개수 체크 */
   n_tab = 0;
   for ( i = 0; *str[i] != '\0'; ++i )
      if ( *str[i] == '\t' )
         ++n_tab;

주석에도 나와 있는 거 같았는데, (*str)[i] 가 되어야겠죠.
이 코드를 보면서 다시한번 느낀 점이지만, 포인터가 정말 파워풀하긴
하지만, 과도한 사용은 좋지 않다는 겁니다. :-)

----
Let's shut up and code.

keedi의 이미지

아... 살려주셔서 감사합니다. 정말 속이 다 시원하군요 ㅠㅠ

당장(free시 세그먼테이셜 폴트)의 가장 큰 오류는 (*str)[i] 였습니다.
그리고 지적해주신 너무나도 중요한 잠재적인 오류들 너무 감사히
참고했습니다.

전 정말 간단한 문제조차도 이렇게 버그가 많을줄 몰랐습니다.
나름대로 초기화도 다 해야지, 포인터 처리도 다 해야지,
메모리할당도 정확하게 해야지... 하면서도 저렇게 많은
부분들이 틀릴줄 몰랐습니다. 어설프게 아는 것이 무섭다는 것도
다시 한번 느끼게 되었습니다.

정말 오늘 다시한번 코딩 습관 스타일, 그리고 특히 앞으로
포인터를 사용할때 주의하고 그리고 또! 또! 한번 더 주의해야
할 점들을 너무나도 많이 배워갑니다.

감사합니다.

yoocj9님 감사합니다.
(*str)[i] 에 대한 지적 감사합니다.
가장 핵심적인 오류 였습니다.

손님님, 서지훈님 감사합니다.
메모리 위치 테스트를 해보았는데 신기하게도
세그폴트가 날때도 메모리 주소는 메인과 read_file()과
dtab()에서 동일했습니다. 아마도 저의 치명적 실수(*str)[i]
가 무언가를 건드렸던 것 같습니다.

hanzo69님 감사합니다.
strcat()시 초기화 문제는 당장은 알 수 없었지만 앞으로
겪게 될 무수한 버그들로 부터 절 살려주셨습니다.
제 C언어에 대한 무지함을 다시 한번 알려 주셨습니다.

sangwoo님 감사합니다.
dtab()내의 malloc()에서 n_tab개수 오류 문제,
k의 초기화 문제, (*str)[i] 문제 체크는 정말
역시 앞으로 겪게 될 무수한 버그로 부터 살려주시고
다시 한번 제 코딩 습관에 대해 생각해보게 되었습니다.

doldori님 감사합니다.
k의 초기화 문제, 파일포인터의 타입 문제 체크
그리고 참고 소스 감사하게 잘 받았습니다.
제 코드에 쓸데 없는 코드만 너무 많았다는 것을 또
깨달았습니다. ㅠㅠ

----
use perl;

Keedi Kim

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.