c언어 메모리 해제 런타임 오류

whdtjr222의 이미지

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define SIZE 1024
 
typedef struct {
	char* movie_title;
	double movie_rate;
}movie_t;
 
int get_movies_num(FILE* fp);
void get_movie_data(movie_t* data, FILE* fp1, FILE* fp2, int movie_data_num);
 
int main(void)
{
	int n; // 영화의 개수 변수
	movie_t* data;
	FILE* fp1;
	FILE* fp2;
	fp1 = fopen("movies.txt", "r");
	if (fp1 == NULL) {
		printf("error");
		exit(1);
	}
	fp2 = fopen("output.txt", "w");
	if (fp2 == NULL) {
		printf("error");
		exit(1);
	}
 
	n = get_movies_num(fp1); // 영화 구조체 개수얻기
	fseek(fp1, 0, SEEK_SET); // fp1 파일의 위치포인터 초기화
	data = (movie_t*)malloc(sizeof(movie_t) * n); // 영화 구조체를 읽어 온 개수 만큼 동적할당
	get_movie_data(data, fp1, fp2, n);
	fclose(fp1);
	fclose(fp2);
	free(data);
}
int get_movies_num(FILE* fp)
{
	char c = 0;
	int count = 0;
	while (c != EOF) {
		c = fgetc(fp);
		if (c == EOF) {
			break;
		}
		if (c == '\n') {
			++count;
		}
	}
	return count/2;
}
void get_movie_data(movie_t* data, FILE* fp1, FILE* fp2, int movie_data_num)
{
	char buffer[SIZE];
	for (int i = 0; i < movie_data_num; i++) { // 파일fp1에서 데이터 읽어서 구조체에 저장
		fgets(buffer, SIZE, fp1);
		buffer[strlen(buffer) - 1] = '\0'; // fgets 함수 \n을 붙여주는걸 \0으로 바꿔줌
		data[i].movie_title = (char*)malloc(sizeof(char) * (strlen(buffer)-1)); // 구조체에서 영화제목 저장변수 길이를 영화제목 길이에 맞게 동적할당
		strcpy(data[i].movie_title, buffer);
		fgets(buffer, SIZE, fp1);
		data[i].movie_rate = atoi(buffer);
	}
	fprintf(fp2, "=================================\n\t\t영화목록\t\t\n=================================\n");
	for (int i = 0; i < movie_data_num; i++) {
		fprintf(fp2, "이름: %s\n", data[i].movie_title);
		fprintf(fp2, "평점: %lf\n", data[i].movie_rate);
	}
	/*for (int i = 0; i < movie_data_num; i++) {
		free(data[i].movie_title);
	}*/
}

c언어 파일 입출력을 공부하던 도중에.. 해당 메모리 해제코드 주석을 해제하면 바로 런타임 에러가 뜹니다.. 그런데 어떤걸 잘못했는지 감이 안와서.. 질문 드립니다.. ㅠㅠ
라스코니의 이미지

글쎄요. free 전에 fflush(stdout); 한번 넣어 보실래요?

whdtjr222의 이미지

어렵네요.. 그래도 파일에 써지긴 해서.. 목적은 이뤘지만요...

익명 사용자의 이미지

data[i].movie_title = (char*)malloc(sizeof(char) * (strlen(buffer)-1)); // 구조체에서 영화제목 저장변수 길이를 영화제목 길이에 맞게 동적할당

이 부분이 문제입니다.

C언어에서 null-terminated string의 메모리 크기는 반드시 strlen으로 얻어진 크기보다 최소 1 커야 하죠. (NUL이 들어가야 하므로)

근데 되려 1 작게 했으니 탈이 나는 것임.

======

현재 구현에서 movie_title에 할당해 줄 메모리의 크기는 SIZE보다 클 이유가 없어요.

그 이상의 입력을 받을 일이 없으니까요.

뭐하러 동적 할당을 합니까?

whdtjr222의 이미지

맞습니다.. 동적할당을 써야한다고 해서 어거지로 넣긴 했습니다 ㅠㅠ

라스코니의 이미지

재밌는 결과네요. 애초에 strcpy에서 뻣어야 할 것 같은데 free() 시험에서 뻣다니 흥미롭네요.
개발환경을 좀 알려주실 수 있나요? 만약 linux라면 valgrind로 수행한 결과(물론 수정 전)도 좀 알려 주세요.

익명 사용자의 이미지

예상했던 결과입니다.
그야 프로그래머 입장에서는 실수했을 때 가능한 한 가까운 데서 에러 내고 죽는 게 편하기야 합니다만...

위 코드가 strcpy에서 죽으려면 쓰기 권한이 없는 영역까지 달려야 하는데,
고작 2바이트 넘친 걸로 그러기엔 불가능까진 아니더라도 쉽지 않죠.

사실 2바이트 정도는 malloc의 주소 정렬을 위한 padding이 커버해 줄 수도 있는데,
제시된 코드에서는 레코드 하나하나 죄다 2바이트씩 넘기고 있으니 한 번 정도는 padding을 벗어나게 됩니다.

결국 이 정도 크기의 힙 오버런은 대개 free에서 문제가 터지게 되는데...
(1) free에 힙 오버런을 체크하는 로직이 있거나 (디버그 환경의 경우)
(2) 넘어간 데이터가 힙 공간을 관리하기 위한 메타데이터 자료구조를 덮어써 버리기 때문입니다.

컴공 전공자라면 malloc/free를 직접 구현해 보는 실습을 할 기회가 있는데,
그걸 해 보고 나면 malloc으로 할당받은 영역을 단 1바이트만 벗어나도 free에서 문제가 될 수 있는 이유를 체득하게 됩니다.

======

free만 하면 에러가 발생한다며 초보자들이 들고 온 코드를 살펴보면 십중팔구 malloc을 필요한 것보다 작게 한 경우입니다.
흔한 실수에요.

whdtjr222의 이미지

많이 공부하고 배우겠습니다! 댓글 너무 감사합니다!

whdtjr222의 이미지

개발환경은 비주얼스튜디오 2022 입니다 그리고 댓글 너무 감사합니다!

라스코니의 이미지

linux에서 malloc()으로 작은 공간을 할당하고 테스트 해보니 아래 같은 말도 안되는 코드도 실행되네요. valgrind로 보니 오류가 있다고 나오고요. 생각외로 heap에 있는 주소 공간은 에러로 잡지를 못하네요.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
int main()
{
    char *str = "Hello,World";
    char *ptr = malloc(strlen(str)-7);
    int i;
 
    for(i=0;i<strlen(str);i++)
        ptr[i] = str[i];
 
    ptr[strlen(str)] = 0;
 
    printf("%s,%lu\n", str, strlen(str));
    printf("%s\n", ptr);
 
    free(ptr);
 
    return 0;
}

반면 로컬 변수로 바꾸면 바로 잡네요. 또 웃긴게 전역 변수로 잡으면 또 못 잡네요. valgrind로도 문제없다고 나옵니다. 흥미롭네요~~

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
int main()
{
    char *str = "Hello,World";
    char ptr[5];
    int i;
 
    for(i=0;i<strlen(str);i++)
        ptr[i] = str[i];
 
    ptr[strlen(str)] = 0;
 
    printf("%s,%lu\n", str, strlen(str));
    printf("%s\n", ptr);
 
    return 0;
}

익명 사용자의 이미지

첫번째 경우는 malloc시 aligned된 크기를 할당해서 4바이트를 요구해도 그보다 큰 크기를 할당합니다.

궁금해서 그런데 전역 변수로 했다는게 어떤식으로 하신건지 알 수 있을까요? 그냥 전역 변수에 malloc으로 할당한건가요?

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.