[getnextline](3)본함수 구현
in 42Seoul on Get_next_line
이번 포스트는 get_next_line프로그램에 본함수에 관한 내용입니다.
1️⃣ 보조함수
- get_next_line함수는 내장함수로는 저수준입출력함수와 동적메모리할당 함수정도만 구현하는 함수이기 때문에 util함수를 사용한다고 해도 코드의 길이가 다소 길 수밖에 없습니다. 그러기 때문에 가독을 높이기 위해서 여러함수로 나눠서 구현을 하였습니다.
- 보조함수에
static
을 주어 이 파일 내에서만 사용할 수 있는 함수로 만들어 줍니다.
2️⃣ 보조함수 구현
1. < str_free >
static void str_free(char *str)
{
free(str);
str = NULL;
}
- get_next_line함수는 malloc함수를 이용하여 동적할당을 해주는 곳이 많기 때문에 free함수를 통해 메모리 해제를 반드시 해줘야 합니다.
- free함수로 메모리 해제를 해준 뒤
str = NULL
로 처리를 하였습니다. 그 이유로는 문자열의 메모리를 해제한 뒤에도 주소값은 유효하기 때문에 str의 주소를 NULL포인터로 지정해주어 추후에 문제가 발생하지 않도록 하기 위해서입니다. 실수로 free함수를 통한 메모리해제를 여러번하게 되면 환경에 따라서 문제가 발생할 수도 있습니다. 이러한 실수로 부터 안전해질 수 있습니다.
2. < find_newline >
static ssize_t find_newline(char *str)
{
ssize_t cnt;
cnt = 0;
while (str != NULL && *str != '\0')
{
if (*str == '\n')
return (cnt);
str++;
cnt++;
}
return (FT_FAIL);
}
- get_next_line은 한줄씩 읽는 함수이기에 한줄을 구분하기 위한 함수가 필요합니다. 문자열을 ‘\n’문자를 구분자로 사용하여 주소의 오프셋을 출력하는 함수를 구현했습니다.
- while문의 두조건의 순서를 바꿔서 다음과 같이 작성한다면
while (*str != '\0' && str != NULL)
&&연산자 우선순위 규칙에 의해서
*str != '\0'
조건 부터 검사를 하게 됩니다. 만약 문자열(str)이 NULL이 들어온다면*str != '\0'
식의 조건을 수행못하고 갑자기 종료 됩니다.(NULL은 ((void*)0)으로 선언 되어 있기 때문에 문자인 ‘\0’을 판독 못합니다.)
동적메모리할당을 이용하는 함수에서 이런식의 갑작스러운 종료는 메모리해제를 해주지 못하기 때문에 치명적입니다. 그렇기 때문에 &&연산자의 우선순위 규칙을 잘고려하여 조건을 배치해야 됩니다. - 이 함수의 매크로 FT_FAIL은 오류를 뜻하는 것이 아니라 ‘\n’문자를 찾지 못했음을 뜻합니다. (다른곳에서 FT_FAIL매크로는 오류매크로로 사용될 예정)
3. < make_line >
static ssize_t make_line(char **line, char **save, ssize_t index)
{
char *temp;
char *next_line;
if (index >= 0)
{
*line = ft_strndup(*save, index);
next_line = *save + index + 1;
temp = ft_strndup(next_line, ft_strlen(next_line));
str_free(*save);
*save = temp;
return (FT_SUCCESS);
}
if (*save == NULL)
*line = ft_strndup("", 0);
else
{
*line = *save;
*save = NULL;
}
return (FT_EOF);
}
- 성공적으로 get_next_line함수가 실행됬을 때 최종적으로 호출되는 함수 입니다.
- 입력받은 배열메모리에 한줄씩 복사해줍니다.(복사할 문자열이 없을시 ‘\0’문자 복사)
- 이 함수를 끝으로 get_next_line함수를 종료합니다. 그렇기 때문에 외부적으로 *line의 매모리 해제가 필요합니다.
if(index >= 0)
문에 들어 왔다는 것은 끝에 줄바꿈 문자가 있다는 뜻이므로 *save의 index만큼의 메모리를 *line에 새로운 메모리로 복사하여 줍니다. 그 뒤 복사한 메모리를 *save에서 없애주는 작업을 해줍니다.- index의 값이 음수일 때는 마지막줄을 뜻합니다. 하지만 마지막줄에 줄바꿈문자가 오게되면 마지막줄은 NULL로 남아있게 됩니다. 그 경우
ft_strndup("", 0);
을 이용하여 복사해 주었습니다. *line = *save
를 통해 주소값을 넘겨준 상태이므로 EOF를 만난 *save는 NULL포인터로 지정해주어 마무리 해줍니다.
3️⃣ 본함수 구현
< get_next_line >
int get_next_line(int fd, char **line)
{
char *buf;
char *temp;
char *save[OPEN_MAX];
ssize_t offset;
ssize_t index;
if (fd < 0 || fd > OPEN_MAX || line == NULL || BUFFER_SIZE <= 0)
return (FT_FAIL);
if ((buf = (char *)malloc(sizeof(char) * (BUFFER_SIZE + 1))) == NULL)
return (FT_FAIL);
while ((index = find_newline(save[fd])) == FT_FAIL && \
(offset = read(fd, buf, BUFFER_SIZE)) > 0)
{
buf[offset] = '\0';
if (save[fd] == NULL)
temp = ft_strndup(buf, offset);
else
temp = ft_strjoin(save[fd], buf);
str_free(save[fd]);
save[fd] = temp;
}
str_free(buf);
if (offset < 0)
return (FT_FAIL);
return (make_line(line, &save[fd], index));
}
- fd(파일디스크립터)는 프로그램을 종료시키지 않는 이상 위치를 기억하고 있습니다. 이 점을 이용하여
char *save[OPEN_MAX];
과 같이 포인터 배열로 선언해주어 여러개의 파일을open함수
를 통해 만든 여러개의fd
로 관리를 할 수 있게 됩니다. - read함수로 줄바꿈문자를 읽을 때까지 while문을 반복하게 됩니다. 하지만 한번에 최대BUFFER_SIZE만큼을 읽어들이기 때문에 줄바꿈이후의 문자들도 읽어 들일 수 있습니다. 그렇기 때문에 *save[OPEN_MAX]라는 임시 저장용버퍼를 만들어 놓고 관리를 하게 됩니다. while문안에서
ft_strndup함수
와ft_strjoin함수
를 적절히 사용하여 *save[OPEN_MAX]버퍼를 정리하는 과정을 거치게 됩니다. - 만약 read함수가 오류가 나게된다면 다음과 같이 FT_FAIL(오류 매크로)를 반환하고 함수를 종료하도록 아래와 같이 작성해 주었습니다.
if (offset < 0) return (FT_FAIL);
하지만 함수가 종료되기 이전에 반드시 동적할당된 buf메모리를 해제 시켜줘야 합니다.
str_free(buf)
를 함수종료 이전에 작성해 줘야 합니다. - 최종적으로 *save[OPEN_MAX]버퍼를 정리하면 make_line함수를 통해 조건에 맞게 *line버퍼에 메모리를 복사해주고 함수를 종료하게 됩니다.
- fd(파일디스크립터)는 함수종료 후에도 사용하지않은 메모리의 위치를 지정하고 있습니다. 그렇기 때문에 함수를 재호출하여 이어서 다음메모리를 관리해줄 수 있습니다.