문제 이해
Get Next Line은 메인 함수에서 파일을 열고, 반복문을 통해 *line 변수 안에 개행 문자를 제외한 문자열을 받을 수 있도록 도와주는 함수입니다.
예를 들어,
Hi, my name is chanhohan
nice to meet you!
파일 안에 위와 같은 문장들이 있다고 하면, get_next_line함수를 이용하여
Hi, my name is chanhohan 과 nice to meet you! 를 구분할 수 있게 됩니다.
read 함수를 통해 구현하면 되는데, buffer 길이 만큼 받아올 수 있다는 점을 이용하면 됩니다.
구현에 앞서 코드 구성 계획
•
Static
우선 static 에 대한 이해가 필요했습니다. 왜냐하면 read를 통해 문자열을 버퍼에 저장을 하고나서 개행문자를 만났을 경우 함수를 끝내야 하는데, buffer를 지역변수로 선언해 버리면 함수가 끝난 다음에는 값이 사라져 버리고 다음 read는 개행 다음이 아닌 buffer만큼 읽은 다음 부분부터 읽어버리기 때문입니다. 따라서 값을 저장할 부분이 필요했는데, 그것이 바로 static 입니다.
static을 통해 변수를 생성하면 그 부분은 데이터 영역으로 들어가기 때문에 문자열을 저장해 놓는 것이 가능합니다. 따라서 문자열을 적절히 저장해 놓고, 다음 get next line 함수를 실행했을 때 저장해 놓은 문자열을 검사해가며 개행문자를 기준으로 나누면 됩니다.
•
조금 더 효율적으로 짜보자
read 함수를 기준으로 반복문을 돌릴 경우 static에 저장된 부분에 개행문자가 포함되어 있는 것을 생각해 보면 굳이 read를 먼저 실행할 필요가 없다고 생각했습니다.
쉽게 말해서, save[fd]에 개행문자가 있는 것이 확실하면 read를 할 필요가 없기 때문에 read 앞에 해당 함수를 넣었습니다.
while ((check = nl_operation(&i, &save[fd], line)) == 1 &&
(num_line = read(fd, buff, BUFFER_SIZE)) > 0)
C
복사
•
nl_operation 함수 (new line operation)
nl_operation은 개행 문자가 들어있는지 확인하고, 들어 있는 경우에는 *line에 문자열을 넣는 함수입니다. 또한 개행문자를 기준으로 다음 문자열이 있으면 해당 문자열의 첫 시작부터를 가리키도록 해주고 이전에 동적할당한 부분은 메모리에서 할당을 해줍니다.
이렇게 구현을 해주면 short circuit evaluation 원칙에 따라서 read 함수는 실행되지 않게 됩니다. while문 안으로 들어왔을 때는, 첫 실행의 경우에 static으로 선언된 변수에 할당된 값이 아무것도 없으므로 libft에서 구현해놓은 ft_strdup를 통해 동적할당을 해주고 그렇지 않은 경우에는 buff의 값과 합쳐줘야 하는데, 이 함수도 역시 앞서 구현해 놓은 ft_strjoin 함수를 이용하여 문자열을 합쳐주도록 합니다.
check변수를 넣어준 이유는, nl_operation에서 동적할당을 실패한 경우 ERROR를 리턴하는데, while문 내에서는 처리하기 힘들기 때문에 변수에 넣었습니다. 후에 이 값은 예외 처리를 해주는 함수 내에서 한 번에 처리됩니다.
예외 처리 및 조심해야할 사항
1.
*line에 동적할당이 무조건 필요합니다. 읽어드릴 파일 내용에 아무 내용이 없어도 동적할당을 해준 후 '\0'을 0번째 인덱스에 넣어줍니다.
2.
본인이 구현한 코드에서 어느 부분에 동적할당이 일어나고, free가 일어나는지 잘 확인합니다. buffer를 동적할당 했다면 gnl함수가 종료되기 전에 free를 해줘야 하고, static 변수로 선언된 것은 eof를 만나기전에 무조건 free가 이뤄져야 합니다.
3.
get_next_line_bonus.c의 해더를 잘 확인해 주어야 합니다. get_next_line.c와 코드가 동일하게 구현했을 경우 cp 명령어를 통해 그대로 복사해 사용하는 경우가 있는데(저가 그랬습니다..) 확인을 잘 해줘야 합니다.
왜 **line 인가?
위 링크에 설명이 잘 되어있는데, 짧게 요약하면 다음과 같습니다.
포인터 변수도 결국 변수이기 때문에, 함수의 매개변수로 값을 전달하면 값이 복사되는 것입니다.
int createData(char *a)
{
a = (char *)malloc(sizeof(char) * 4)
}
int main()
{
char *p = NULL;
createData(p);
}
C
복사
위와 같은 코드가 있다고 한다고 합시다.
main문 안의 포인터 변수 p는 메모리 상 임의의 공간에 위치해 있습니다.
p는 NULL을 가리키고 있으므로 p에는 NULL이 들어있습니다.
이제 createData의 인자로 이 p가 가지고 있는 NULL을 받게 되는데 이 포인터 변수 a 역시 임의의 공간에 존재합니다.
a가 가리키고 있는 공간이랑 p가 가리키고 있는 공간이 서로 같으니까 a에서 동적할당을 하면 p도 그 공간을 가리키는 것이 아닌가 라는 착각이 들기 때문에 조심해야 합니다.
createData에서의 매개변수 a와, main문 에서의 변수 p는 서로 다른 공간에 위치합니다. 따라서 a가 동적할당으로 메모리 공간을 생성했다고 해서 main문 안의 p가 그 공간을 가리키는 것이 아니라는 것입니다.
createData함수 내에서도 main문 안의 p의 값을 바꾸고 싶다면, p의 주소를 보내주면 됩니다.
int createData(char **a)
{
*a = (char *)malloc(sizeof(char) * 4)
}
int main()
{
char *p = NULL;
createData(&p);
}
C
복사
createData의 a는 이제 p의 주소를 가리키게 되므로 역참조를 통해 온전히 p의 역할을 수행할 수 있게 됩니다.
Main문
테스트에 자주 사용했던 main문 입니다.
(ex > gcc -Wall -Wextra -Werror main.c get_next_line.c get_next_line_utils.c -D BUFFER_SIZE=32)
#include "get_next_line.h"
#include <fcntl.h>
#include <stdio.h>
void ft_putstr(char *s)
{
if (!s)
return ;
while (*s)
write(1, s++, 1);
}
int main(int argc, char **argv)
{
int fd;
int ret;
char *str;
str = NULL;
if (argc < 2)
ft_putstr("File name missing.\n");
else if (argc > 2)
ft_putstr("Too many arguments.\n");
else
{
fd = open(argv[1], O_RDONLY);
if (fd == -1)
{
ft_putstr("Error, cannot open file\n");
return (1);
}
ft_putstr("File:\n\n");
while ((ret = get_next_line(fd, &str)) != 0)
{
printf("[%s]\n", str);
free(str);
}
printf("[%s]\n", str);
free(str);
ft_putstr("\n\n:End of File\n");
if (ret == -1)
{
ft_putstr("Error, cannot close file\n");
return (1);
}
}
return (0);
}
C
복사