[gnl] get_next_line - 연결 리스트
get_next_line 구성
get_next_line 과제에서는 총 세가지 파일을 제출해야한다.
get_next_line.c, get_next_line_utils.c, get_next_line.h.  
libft를 사용할 수 없는 대신 _utils.c가 허용되어 총 10개의 함수를 사용할 수 있다.  
연결 리스트를 이용하여 기능을 구현하였으며, 사용한 함수들은 다음과 같다.
char	*get_next_line(int fd)
t_list	*add_fd_remain(t_list *remain, int fd)
static char	*check_free(t_list *rm, t_list **head)
static char	*check_remain(int fd, t_list *remain)
static char	*make_next_line(char *remain)
static char	*update_remain(char *remain)
size_t	ft_strlen(const char *str);
char	*ft_strchr(const char *str, int c);
char	*ft_strdup(const char *src);
char	*ft_strjoin(char const *str1, char const *str2);
코드 분석
리스트의 노드로 사용될 구조체의 구성 요소는 다음과 같다.
typedef struct s_list
{
	char			*str;
	int				fd;
	int				flag;
	struct s_list	*next;
}	t_list;
- 파일에서 읽어들일 문자열
- fd값
- EOF에 도달했는지 여부를 판단할 flag값
- 다음 리스트로 연결할 구조체 포인터 next
get_next_line 함수
char	*get_next_line(int fd)
{
	static t_list	*head;
	t_list			*remain;
	char			*next_line;
	if (fd < 0 || BUFFER_SIZE <= 0)
		return (NULL);
	if (!head)
	{	
		head = (t_list *)malloc(sizeof(t_list));
		head->next = NULL;
		head->fd = -1;
	}
	remain = add_fd_remain(head, fd);
	if (!check_remain(remain, fd))
		return (NULL);
	else
		next_line = make_next_line(ft_strdup(remain->str));
	remain->str = update_remain(remain->str);
	if (!remain->str)
		check_free(head, &head);
	return (next_line);
}
문자열을 정적 변수를 선언했던 것과 달리, 이번엔 리스트의 head 노드를 정적 변수로 선언해주었다.  
따라서 get_next_line 함수가 실행될 때마다 head 노드를 참조하여 원하는 기능을 수행하게 된다.  
get_next_line함수의 작동과정은 다음과 같다.
- 인자로 받아온 fd값과, 컴파일시-D옵션으로 지정한 변수BUFFER_SIZE의 값을 체크한다.fd < 0이거나BUFFER_SIZE <= 0이라면 정상적인 입력이 아니므로NULL을 리턴한다.
- head노드가- NULL이라면 새롭게 할당한다. 이 때- head노드의- fd값은- head라는 것을 지정해주기 위해 -1로 설정한다.
- add_fd_remain함수를 통해- fd값에 해당하는- remain노드를 결정하고,- remain노드에 담길 변수들의 값을 설정한다.
- check_remain함수를 통해 파일에서 데이터를 읽어- remain->str에 저장한다. 만약 저장할 데이터가 없다면- NULL을 반환한다.
- remain->str이 있다면- ft_strdup함수로 메모리를 새롭게 할당 후- remain->str을 복사하고,- make_next_line함수를 통해 복사된 문자열에서 첫- \n까지의 한 줄을 잘라내어- next_line에 저장한다.
- next_line이- NULL이 아니라면- ft_strdup함수를 이용해 새롭게 메모리를 할당한다. 이는 추후- remain의 메모리가 해제될 경우를 대비하기 위함이다.
- update_remain함수를 통해- remain->str에서 ` next_line`으로 저장할 한 줄을 빼고 나머지 문자열을 다시 저장한다.
- 갱신 후 remain->str이 비어있다면check_free함수를 통해 필요없어진 메모리들을 해제하는 작업을 수행한다.
- 만들어놓았던 next_line을 반환한다.
add_fd_remain 함수
t_list	*add_fd_remain(t_list *remain, int fd)
{
	t_list	*new;
	new = (t_list *)malloc(sizeof(t_list));
	new->str = NULL;
	new->fd = fd;
	new->next = NULL;
	new->flag = 1;
	while (remain)
	{
		if (remain->fd == fd)
		{
			free(new);
			break ;
		}
		if (!remain->next)
		{
			remain->next = new;
			return (new);
		}
		remain = remain->next;
	}
	return (remain);
}
fd값에 대응하는 노드를 만드는 함수다.  
add_fd_remain 함수의 작동과정은 다음과 같다.
- 새로운 new노드를 할당하고,new->fd값을 인자로 받은fd값으로 저장한다.
- 인자로 받아온 remain은head노드부터 시작되는 리스트이다. 노드를 이동시켜가면서 반복문을 돌린다.
- remain->fd가 인자로 받은- fd값과 일치한다면 이미- fd값에 해당하는 노드가 존재하는 것이므로 할당했던- new노드를 해제하고 반복문을 빠져나온 후 해당하는 노드의 위치를 반환한다.
- fd값을 가지고있는 노드를 찾지 못하고 리스트의 끝까지 돈다면 새로운 노드가 필요한 것이므로, 기존에 존재하던 리스트 맨 뒤에- new노드를 붙여주고 반환한다.
따라서 add_fd_remain으로 반환된 노드는 get_next_line 함수의 인자로 주어진 fd값을 가진 노드가 되어 여러 fd값이 주어지더라도 구분할 수 있게 된다.
check_remain 함수
static char	*check_remain(t_list *remain, int fd)
{
	char	*buff;
	char	*temp;
	int		read_idx;
	buff = (char *)malloc((BUFFER_SIZE + 1) * sizeof(char));
	while (!remain->str || !ft_strchr(remain->str, '\n'))
	{
		read_idx = read(fd, buff, BUFFER_SIZE);
		if (read_idx <= 0)
		{
			remain->flag = -1;
			break ;
		}
		buff[read_idx] = '\0';
		if (!remain->str)
			remain->str = ft_strdup(buff);
		else
		{	
			temp = ft_strjoin(remain->str, buff);
			free(remain->str);
			remain->str = temp;
		}
	}
	free(buff);
	return (remain->str);
}
fd에서 데이터를 읽어들여 노드의 str 변수에 저장하는 함수다.  
check_remain 함수의 작동과정은 다음과 같다.
- 변수 buff를BUFFER_SIZE+ 1(\0문자가 들어갈 공간)만큼 할당한다.
- remain->str이 없거나,- remain->str에 남아있는 문자열에- \n이 없을 경우 반복문으로 들어간다.
- read함수로- fd에서- BUFFER_SIZE바이트만큼 데이터를 읽어와- buff에 저장한다.
 이 때- read함수의 반환값은 정상적으로 읽어들인 바이트의 크기가 되므로 이를- read_idx변수에 저장해 0보다 작으면- fd에 해당하는 파일을 끝까지 읽은것이다.
 따라서- remain->flag값을 -1로 바꿈으로써 이를 반영한 후 반복문을 빠져나온다.
- 그렇지 않으면 읽어낸 데이터를 다루기 위해 buff[read_idx], 즉buff의 마지막에\0을 넣어준다.
- 만약 remain->str이 빈 상태였다면ft_strdup함수를 이용해remain에 새롭게 메모리를 할당하며buff를 복사해준다.
- remain->str에 남아있는 데이터가 있었다면- ft_strjoin함수를 이용해- remain->str뒤에- buff를 붙인 새로운 문자열을 할당한다.- 이 때 ft_strjoin함수로 반환되는 문자열은 기존의remain->str과 다른 메모리를 가지고 있으므로, 메모리 누수를 피하기 위해서 기존remain->str의 메모리를 해제한 후 새롭게 만든 메모리로 연결시켜주어야 한다.
 
- 이 때 
- remain->str에서- \n을 찾을 수 있을 때까지 위 과정을 반복한다. 물론 중간의 탈출조건으로 반복문을 빠져나올 수도 있다.
- 역할이 끝난 buff의 메모리를 해제한 후,remain->str을 반환한다.
이 결과 만들어지는 remain->str은 셋 중 하나이다.
- \n이 반드시 하나 이상 포함되어있는 문자열
- \n이 없고,- EOF가 포함되어있는 문자열
- NULL(처음부터- remain->str이 비어있었고,- read함수로 읽어올 데이터도 남아있지 않았을 경우)
배열로 구성했을 때와 다른점은 파일을 다 읽은 경우 remain->flag값을 설정해주었다는 것 뿐이다.
make_next_line 함수
static char	*make_next_line(char *str)
{
	char	*next_line;
	int		len;
	if (!str)
		return (NULL);
	len = 0;
	while (*(str + len))
	{
		if (*(str + len++) == '\n')
			break ;
	}
	next_line = (char *)malloc((len + 1) * sizeof(char));
	*(next_line + len) = '\0';
	while (--len >= 0)
		*(next_line + len) = *(str + len);
	free(str);
	return(next_line);
}
make_next_line 함수의 작동과정은 다음과 같다.
- str을 읽으면서- next_line에 할당할 길이를 잰다.- \n가 있다면 거기서 멈추고, 없다면- str의 끝까지 잰다.- \n가 없는 경우는 파일을 끝까지 읽은 것이다.
- 잰 길이로 next_line에 메모리를 할당한다.
- next_line에- str의 문자열을 길이만큼 복사하고 마지막에- \0을 넣는다.
- 기존 str의 메모리를 해제하고, 만들어진next_line을 반환한다.
배열로 구성했을 때와 기능상 달라진점은 전혀 없다.  
다만 함수 갯수의 제한때문에 ft_strlcpy함수를 사용하지 못하게 되어 make_next_line 함수 안에 직접 집어넣었다.
update_remain 함수
static char	*update_remain(char *remain_str)
{
	char	*new;
	char	*fix_remain_str;
	int		idx;
	int		len;
	idx = -1;
	fix_remain_str = ft_strchr(remain_str, '\n');
	if (!fix_remain_str || !(ft_strlen(fix_remain_str) - 1))
	{
		free(remain_str);
		return (NULL);
	}
	len = ft_strlen(++fix_remain_str);
	new = (char *)malloc((len + 1) * sizeof(char));
	while (len > ++idx && *(fix_remain_str + idx))
		*(new + idx) = *(fix_remain_str + idx);
	*(new + idx) = 0;
	free(remain_str);
	return (new);
}
remian->str에서 next_line으로 출력할 문자열을 빼는 함수다.  
update_remain 함수의 작동과정은 다음과 같다.
- ft_strchr함수를 이용해- remain->str에서- \n의 위치를 찾아- fix_remain_str변수에 저장한다.
- 찾을 수 없다면(fix_remain == NULL) 파일을 끝까지 읽은 경우이다. 또는,fix_remain_str의 길이가 1인 경우\n뒤에 문자열이 없는 경우이다. 두 경우 모두 더이상remain->str을 남길 이유가 없으니 메모리를 해제한 후NULL을 반환한다.- 배열으로 함수를 짰을 때와는 달리 두번째 조건이 추가된 이유는, 함수 갯수의 제한으로 인해 ft_strlcpy함수를 사용하지 않기 때문이다.
 
- 배열으로 함수를 짰을 때와는 달리 두번째 조건이 추가된 이유는, 함수 갯수의 제한으로 인해 
- 찾은 경우엔 남은 부분을 저장할 메모리를 변수 new에 새롭게 할당한다. 이 때fix_remain_str을 한칸 뒤로 옮겨\n다음의 문자열부터 시작되도록 하고, 그 길이를 변수len에 저장한 후 이를 할당할 메모리 크기로 사용한다.
- new에- fix_remain_str의 문자열을 복사하고, 마지막에는- \0을 넣어준다.- ft_strlcpy함수를 사용한 것과 같다.
- 기존의 remain->str을 해제하고,new를 반환한다.
check_free 함수
static char	*check_free(t_list *rm, t_list **head)
{
	t_list	*temp;
	while (rm)
	{
		if (!rm->next)
			break ;
		if (!(rm->next)->str && (rm->next)->flag == -1 && !(rm->next)->next)
		{
			free(rm->next);
			rm->next = NULL;
		}
		else if (!(rm->next)->str && (rm->next)->flag == -1 && (rm->next)->next)
		{
			temp = rm->next;
			rm->next = (rm->next)->next;
			free(temp);
		}
		rm = rm->next;
	}
	if (!(*head)->next)
	{
		free(*head);
		*head = NULL;
	}
	return (NULL);
}
메모리 누수를 막기 위해 필요없어진 메모리들을 해제시켜주는 함수이다.  
check_free 함수의 작동과정은 다음과 같다.
- head노드부터 시작해 노드가 있다면 노드를 넘겨가면서 반복문을 돈다.
- 만약 다음 노드가 없다면 반복문을 빠져나온다.
- 다음 노드가 존재하지만 문자열이 없고, flag값이EOF에 도달했다는 의미인 -1인 경우 두가지 경우로 나뉜다.- 다다음 노드가 없는 경우 다음 노드가 마지막 노드이므로 다음 노드를 해제한 후 현재 노드의 next를NULL로 지정한다.
- 다다음 노드가 존재하는 경우 다음 노드는 중간에 낀 노드이므로, 현재 노드와 다다음 노드를 연결시킨 후 다음 노드를 해제한다.
 
- 다다음 노드가 없는 경우 다음 노드가 마지막 노드이므로 다음 노드를 해제한 후 현재 노드의 
- 반복문에서 빠져나온 후, 만약 head노드의 다음 노드가 없다면 모든fd에 해당하는 노드들이 해제된 것이므로 추가로get_next_line함수가 호출되지 않는 한head노드 또한 유지될 필요가 없다. 따라서head노드의 메모리를 해제한 후NULL을 반환한다.- 이 때 head노드 포인터에NULL을 넣어주지 않으면 다음번get_next_line이 호출되었을 시head노드 포인터는 여전히 해제된 메모리 주소를 가리키고있어 문제가 발생한다.
 
- 이 때 
flag값을 사용하지 않고, check_remain함수에서 사용한 read_idx을 인자로 주고받으며 사용하는 방법도 가능할 것 같다.
