20210111(월) - jungcow/42Cursus GitHub Wiki

1. 학습날짜

  • 2021-01-11(월)

2. 학습시간

  • 16:00 ~ 20:00 / 22:30 ~ 03:30 (집)

3. 학습범위 및 주제

  • get_next_line의 mandatory와 bonus 구현

4. 동료 학습 방법

  • discord를 통해 seunghoh님과의 질의응답(get_next_line 초기화 관련 질의응답)

5. 학습 목표

  • 메모리 릭을 철저히 관리
  • bonus까지 구현

6. 과제 제출 repository 주소

http://13.125.198.2:3000/jungwkim/get_next_line.git

7. 상세 학습 내용

get_next_line

  1. Prototype :int get_next_line(int fd, char **line);

  2. Parameters

    1. fd : 프로그램이 읽을 파일 디스크립터(file descripter)
    2. 한 줄을 읽은 후 넣을 문자열의 주소
  3. Return value

    1. 1: 한 라인이 읽혔을 때.
    2. 0: EOF에 도달했을 때
    3. -1: 에러가 발생했을 때
  4. External functs

    1. read() -> <unistd.h>
    2. malloc() -> <stdlib.h>
    3. free() -> <ftdlib.h>
  5. Description

파일 디스크럽터로부터 읽어 온 하나의 라인(newline 없이)을 반환하는 함수 작성

main 함수 유추 && 구현

int main(void)
{
	char *line;
	int fd;
	int fd2;
	int fd3;
	int fd4;
	int		len;

	fd = open("example.txt", O_CREAT | O_RDONLY, 0777);
	fd2 = open("example2.txt", O_CREAT | O_RDONLY, 0777);
	fd3 = open("example3.txt", O_CREAT | O_RDONLY, 0777);
	fd4 = open("example4.txt", O_CREAT | O_RDONLY, 0777);
	if (fd == -1)
		return (1);
	fd = 0;
	//fd2 = 0;
	while ((len = get_next_line(fd, &line)) == 1)
	{
		printf("%d, %s\n", len,  line);
		free(line);
	}
	printf("%d, %s\n", len, line);
	while ((len = get_next_line(fd2, &line)) == 1)
	{
		printf("%d, %s\n", len,  line);
		free(line);
	}
	printf("%d, %s\n", len, line);
	while ((len = get_next_line(fd3, &line)) == 1)
	{
		printf("%d, %s\n", len,  line);
		free(line);
	}
	printf("%d, %s\n", len, line);
	while ((len = get_next_line(fd4, &line)) == 1)
	{
		printf("%d, %s\n", len,  line);
		free(line);
	}
	printf("%d, %s\n", len, line);

	/*
	line = "abcde";
	len = get_next_line(fd, &line);
	printf("%d, %s\n", len, line);
	free(line);
	len = get_next_line(fd2, &line);
	printf("%d, %s\n", len, line);
	free(line);
	len = get_next_line(fd3, &line);
	printf("%d, %s\n", len, line);
	free(line);
	len = get_next_line(fd2, &line);
	printf("%d, %s\n", len, line);
	free(line);
	len = get_next_line(fd3, &line);
	printf("%d, %s\n", len, line);
	free(line);*/
	close(fd);
	close(fd2);
	close(fd3);
	return (0);
}/

** 1. 여러개의 fd가 섞여서 들어올 때** ** 2. fd가 차례대로 파일의 끝까지 읽어들일 때.

메모리 heap영역의 내부 매커니즘

  • heap 영역이 여러가지 버퍼 사이즈가 설정되어있는 리스트로 이루어진 구조로 되어있고,
  • malloc을 함수에서 호출하게 되면, heap영역에서 해당하는 버퍼사이즈가 설정되어있는 영역을 할당해준다.
  • 이 때, heap영역에서 해당하는 버퍼사이즈가 없을 경우(동적할당하려는 버퍼사이즈가 훨씬 큰 경우) 리스트들을 엮어서 해당하는 버퍼사이즈로 만들고 해당하는 메모리를 할당해준다.
  • free()를 해준다는 의미는 다시 heap영역에서 할당가능한 리스트로 되돌아가게 만들어줌을 의미함.

메모리 릭 관련 처리

0. 메모리 릭이 나는 이유.

  • heap 영역에 동적할당을 시켜준 데이터들을 free를 안시켜줄시 메모리가 함수가 끝나도 그대로 남아있어, 메모리 릭이 나게 된다.
  • 모든 동적할당한 데이터들은 사용한 후에는 반드시 free를 해주어야 한다.

0. 메모리 릭 검사 방법.

  • main함수에서 while(1);로 함수가 끝나는 것을 강제로 막아준다.
  • ./a.out으로 프로그램을 실행
  • 다른 터미널을 열어서 leaks a.out 명령어를 친다.
  • 메모리 릭이 나는 주솟값과 그 안에 들어있는 데이터 값을 얻을 수 있다.

1. read_buffer함수에서 오류가 날 시

  • malloc이 실패했거나, read()에서 오류가 나서 -1을 반환할 때, 지금 fd를 포함한 구조체 포인터는 물론, 모든 구조체 포인터들을 free()시켜준다.
void		clear_buffer(t_backup **backup)
{
	t_backup *ptr;
	t_backup *tmp;

	ptr = *backup;
	while (ptr)
	{
		tmp = ptr->next;
		free(ptr->str);
		free(ptr->tmp);
		free(ptr);
		ptr = tmp;
	}
	ptr = NULL;
}

2. file을 전부 line에 넣어 반환한 후 개행문자가 없이 문자열과 EOF만 남았을 시

  • 링크드 리스트에서 해당 fd에 해당하는 구조체를 삭제하고 전의 구조체와 *next를 연결해준다.
void		del_buffer(int fd, t_backup **backup)
{
	t_backup *ptr;
	t_backup *before;

	ptr = *backup;
	if (ptr->fd == fd)
	{
		*backup = ptr->next;
		free(ptr->tmp);
		free(ptr);
		return ;
	}
	while (ptr)
	{
		if (ptr->fd == fd)
		{
			before->next = ptr->next;
			free(ptr->tmp);
			free(ptr);
			break ;
		}
		before = ptr;
		ptr = ptr->next;
	}
}

3. 많이 헤멨던 메모리 릭 관련 문제(1)

  • 아래 ptr은 잠시 str의 데이터들을 받아놓는 임시 저장소 기능을 한다.
  • 이 때, ptr은 이 함수에서 기능을 다 쓰기 때문에 여기서 free를 해주어야 했는데 해주지 않아 메모리 릭이 계속 났었다.
int			alloc_line(char **line, t_backup *backup, ssize_t flag)
{
	char *ptr;

	backup->sum -= flag + 1;
	if (!(*line = ft_strndup(backup->str, flag)))
		return (0);
	if (!(ptr = ft_strndup(backup->str + flag + 1, backup->sum)))
		return (0);
	free(backup->str);
	if (!(backup->tmp = (char *)malloc(sizeof(char) * backup->sum)))
		return (0);
	ft_memcpy(backup->tmp, ptr, backup->sum);
	free(ptr);
	return (1);
}

4. 많이 헤멨던 메모리 릭 관련 문제(2)

  • main함수에서 get_next_line을 호출하여 line에 데이터들을 담은 후에 바로 printf를 한후 다음 루프로 넘어가지 말고 그전에 line을 free를 해주어야 한다.
  • testcase를 짤 때의 문제점이었고, testcase를 구성할 때, 이런 메모리 leaks가 나는 것을 조심하자.

그 전 학습에서의 문제점

> ./a.out
fd : 3
hello
after gnl line: hello
world
after gnl line: world
greate world babvy
after gnl line: greate world babvy
soso agasdf ------------------------------> 여기서 ctrl + D를 눌렀다.
after gnl line: soso agasdf
afger gnl line: --------------------------> 그러자 여기서 하나가 더 생긴다.(문제점)
  • 위와 같은 결과는 당연한 것.
  • 개행 문자가 있을 시 after gnl line: 후에 line이 출력 된다.
  • 그 이후 남은 문자는 EOF 하나뿐이다. 이 때, get_next_line은 0을 반환.
  • 따라서 after gnl line: 후에 line에는 널값만 들어오게 된다.

8. 학습에 대한 총평

  • get_next_line 함수를 다루면서 memory leak관련해서 많이 배우게 되었다.
  • malloc과 free 그리고 포인터를 많이 다루는 거다 보니까 데이터가 할당된 heap영역의 주소를 free해서 다른 프로그램들이 할당 가능한 상태로 만들어줘야 메모리 릭이 나지 않는다는 것을 알았다.
  • get_next_line을 끝내고, 든 생각은 잘하는 사람이 옆에 있냐 없냐가 큰 차이가 있었다는 것이다. 동료평가가 잘하는 사람에게 물어보며 학습하는 방식으로 알고 있다. 하지만, 있고 없고의 차이가 너무 큰 것에 대해 자신에게 큰 실망을 하게 된 것 같다.

9. 다음 학습 계획

  • ft_printf 문제를 읽고 문제 이해하기
  • testcase를 구성하여 빠르게 로직 짜기
  • get_next_line을 다시 한번 처음부터 끝까지 짜보기
  • notion에 핵심 기능을 정리하기
  • notion에 로직 정리하기
  • notion에 예외 처리 사항 정리하기
⚠️ **GitHub.com Fallback** ⚠️