free함수와 포인터(pointer)에 대하여

고리·2022년 7월 21일
1

CS

목록 보기
5/6

42서울 과제 중 get_next_line이란 과제를 하던 중 free 함수를 사용할 일이 있었는데

어마어마한 삽질을 하게 되어 기록하려고 한다. free 함수 사용하려다 세 번 막혔는데


첫 번째 문제는 이러했다. (코드가 길어서 핵심만 간단히)

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

int main(void)
{
	char	*ptr = "This is gori's test!\n";
	char	*tmp;

	tmp = strdup(ptr); // ptr과 똑같은 문자열을 tmp에 동적할당
	free(ptr);
	printf("%s", tmp);
	return (0);
}

다들 결괏값을 예상해보자

This is gori's test!

를 예상했다면 같은 실수를 한 것이다.

cplusplus.com의 free()함수의 설명에는 이렇게 쓰여있다.

A block of memory previously allocated by a call to malloc, calloc or realloc is deallocated, making it available again for further allocations.

If ptr does not point to a block of memory allocated with the above functions, it causes undefined behavior.

If ptr is a null pointer, the function does nothing.

Notice that this function does not change the value of ptr itself, hence it still points to the same (now invalid) location.

첫 번째 줄에는 malloc, calloc, realloc으로 할당된 메모리 공간이 해제된다고 쓰여있다. 위의 코드에서는 앞의 함수들로 동적할당 하지 않은 값을 free했기 때문에 문제가 생겼던 것이다.

1.5번째 문제로 free된 pointer를 다시 free해도 동일한 문제가 발생한다...

이때 설명을 끝까지 읽었어야 했는데... 설명을 끝까지 읽지 않아서 세 번째 문제가 생겼다.


세 번째 문제로 가기 전에

두 번째 문제를 살펴보자.

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

void	free_ptr(void *ptr)
{
	printf("being freed... %p\n\n", ptr);
	free(ptr);
}

int main(void)
{
	char	*ptr, *str = "HELLO";

	ptr = (char *) malloc(sizeof(char) * (6));
	strcpy(ptr, str); // str을 ptr에 복사

	printf("before ptr: %s\n", ptr);
	printf("before address: %p\n\n", ptr);

	free_ptr((void *)ptr);

	printf("after ptr: %s\n", ptr);
	printf("after address: %p\n", ptr);
	return (0);
}

결과값을 예측해보자

before ptr: HELLO
before address: 0x7fede4c05800

being freed... 0x7fede4c05800

after ptr: (null)
after address: 0x0

위와 같이 생각했다면 틀렸다..!
실제 결과 값은 다음과 같다.

아니 이게 어찌 된 일일까? 나는 분명 free를 했는데...

출력값을 확인하면 주솟값을 가진 포인터 변수가 잘 넘어갔음에도 불구하고 여전히 해당 주소를 가리키고 있다.
메인 함수의 값을 다른 함수를 통해서 바꾸고 싶다면 pass by reference형태로 전달해 주소 값을 참조한 후 *연산자로 값을 직접 바꿔주어야 한다. 이 정도는 알고 있었지만 c언어에는 pass by reference란건 없다는 사실을 몰랐다..

이게 무슨 소리야..?

이곳의 첫 번째 답변을 보면 다음과 같이 적혀있다.

C only supports pass by value. The formal argument in the function definition is always a different object in memory from the actual argument in the function call (assuming the actual argument is itself an object, which it doesn't have to be).

formal argument와 actual argument는 메모리상에서 항상 다른 객체라고 한다.
나의 main함수에서

free_ptr((void *)ptr);

이렇게 주소를 전달했을 때 ptr의 주소는

void	free_ptr(void *ptr)

이곳으로 복사되어 전달된다. 흔히 fake pass by reference라고 부르는 듯 하다.


여기까지 이해했을 때 한 가지 의문점이 들었다.
free_ptr함수에서 찍어본 주소 값도 같았는데 같은 주소값을 free하면 main함수에서 같은 주소값을 가진 ptr도 free 되는 게 아닐까..?

정답은 그렇다였다. 주소 값을 복사해 넘기는 것과 주소 값의 주소값을 전달해 *연산자로 주소 값을 참조하는 것은 정확히 동일한 동작이다.

free함수는 단지 인자로 전달된 메모리 공간을 할당 해제해주기 때문에 fake pass by referencereal pass by reference든 상관하지 않는다.

때문에 다음의 코드는 위와 정확히 동일한 기능을 한다.

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

void	free_ptr(void **ptr)
{
	printf("being freed... %p\n", *ptr);
	printf("double pointer: %p\n\n", ptr);
	free(*ptr);
}

int main(void)
{
	char	*ptr, *str = "HELLO";

	ptr = (char *) malloc(sizeof(char) * (6));
	strcpy(ptr, str); // str을 ptr에 복사

	printf("before ptr: %s\n", ptr);
	printf("before address: %p\n\n", ptr);

	free_ptr((void **)&ptr);

	printf("after ptr: %s\n", ptr);
	printf("after address: %p\n", ptr);
	return (0);
}


이제 세번째이자 마지막 문제를 보자.

인자만 동일하다면 free함수는 그 인자가 어떻게 전달 되었든 동일한 기능을 하는 것을 확인 했다.
혹시 제대로 free되지 않았나 확인하기 위해 system콜을 사용해 보았다.

	system("leaks a.out"); // a.out실행파일의 메모리 누수를 검사


마지막 줄을 보면 leak없이 할당한 memory가 free되어 실행이 종료된 것을 확인할 수 있다.

free는 잘 했는데 왜 주소값과 저장된 문자열이 그대로일까?

cplusplus.com의 설명중 마지막 줄을 보면 다음과 같이 써있다.

Notice that this function does not change the value of ptr itself, hence it still points to the same (now invalid) location.

free함수는 ptr에 저장된 값을 바꾸지 못하고 ptr은 여전히 같은 메모리 공간을 가리키고 있다고 한다.
이것을 Dangling pointer라한다.
즉 객체에 대한 참조가 포인터 값에 대한 수정 없이 삭제되거나 할당 해제돼서 포인터가 계속 할당 해제된 메모리를 가리킬 때이다.

free()는 해당 포인터가 가리키는 메모리공간을 할당 가능하게 해줄 뿐 전달된 포인터 변수에는 아무짓도 하지 않는다. 때문에 아무런 처리 없이 메모리를 할당하다가 위의 메모리공간을 할당하게 된다면 예상치 못한 결과가 발생해 segmentation오류를 유발한다.

위키에 매우 자세히 설명이 되어있는데 읽어보면 이 문제를 어떻게 해결해야할지 감이 잡힌다.

바로 ptr이 가리키는 메모리 공간을 free해준 후 ptr을 NULL로 만들어 주는 것이다.
바로 이렇게

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

void	free_ptr(void **ptr)
{
	printf("being freed... %p\n\n", *ptr);
	*ptr = NULL;
	free(*ptr);
}

int main(void)
{
	char	*ptr, *str = "HELLO";

	ptr = (char *) malloc(sizeof(char) * (6));
	strcpy(ptr, str); // str을 ptr에 복사

	printf("before ptr: %s\n", ptr);
	printf("before address: %p\n\n", ptr);

	free_ptr((void **)&ptr);

	printf("after ptr: %s\n", ptr);
	printf("after address: %p\n", ptr);
	return (0);
}

결과 값은 어떻게 될까?

드디어 처음에 생각했던 동작을 한다..!

얼마나 많은 길을 돌아돌아왔는지.. 아찔하다.. 다시는 까먹지 말아야지....**
혹시 같은 증상을 겪은 사람이 있다면 도움이 되었으면 좋겠다.

profile
Back-End Developer

0개의 댓글