GET_NEXT_LINE - Mandatory
GET_NEXT_LINE
Reading a line on a fd is way too tedious
fd에서 한 줄을 읽는다는 것은 너무나도 지루한 일입니다.
Summary: The aim of this project is to make you code a function that returns a line, read from a file descriptor.
요약: 이 프로젝트의 목적은 파일 디스크립터로부터 읽혀진, 개행으로 끝나는 한 줄을 반환하는 함수를 코드화 하는 것입니다.
개요
1 서클 3개의 과제 born2beroot, ft_printf, get_next_line 중 gnl은 대부분의 카뎃이 가장 먼저 시작하는 과제입니다. 제 생각에는 이 과제가 상대적으로 가장 쉬워서 그런 것 같습니다. 하지만 그렇게 쉽게 풀리지만은 않을 겁니다.
gnl이 어려운 이유는 버퍼의 처리 과정을 이해하고 활용하는 점도 있지만, 온갖 메모리할당으로 늘어난 포인터를 해제해야 하기 때문입니다. 또한 제출할 수 있는 소스 코드 파일이 2개이므로, 사용할 수 있는 함수는 10개가 제한입니다. 0 서클의 첫 과제인 Libft에서 작성한 함수를 사용하면 더 쉽게 해결할 수 있습니다. 하지만, Libft에서 가져올 소스코드가 다른 함수를 사용했다면, 사용한 다른 함수도 같이 가져와야 하므로, Libft를 수정해야 할 수도 있습니다.
해당 과제는 파일 디스크립터와 정적변수에 대한 개념, free 함수의 동작, 활용 등을 학습할 수 있습니다. 학습 커리큘럼에 따라 gnl은 다른 과제에서도 사용되므로, 코드를 작성한 뒤, 동작 방식을 확실히 이해하고 효율적으로 만드는 것이 중요하다고 생각합니다.
목표
This project will not only allow you to add a very convenient function to your collection, but it will also allow you to learn a highly interesting new concept in C programming: static variables.
이 프로젝트는 당신의 콜렉션(아마 라이브러리)에 아주 편리한 함수를 추가하게 할 뿐만 아니라, C 프로그래밍에 있어서 아주 흥미롭고 새로운 개념인 '정적 변수'를 배울 수 있도록 도울 것입니다.
주어진 인자(fd)를 읽고, 한 줄을 반환하는 함수를 만들어야 합니다.
과제의 요구 조건을 살펴봅시다.
1. 반환 값은 한 줄이 제대로 읽혔을 경우 읽힌 라인 그 한 줄을 반환해야 합니다.
2. 읽을 라인이 더 이상 없거나(전부 읽거나) 에러 발생 시 반환 값은 NULL입니다.
+ 에러 발생은 read 함수의 동작을 가리키는 것 같습니다.
3. 사용 가능한 외부 함수: read, malloc, free
4. 파일에서 읽을 때, 표준입력으로부터 읽어 들일 때 모두 함수가 예상대로 동작하는지 확인해야 합니다.
* 파일 뿐만 아니라 표준입력(stdin)의 경우에서도 올바르게 작동할 수 있어야 합니다.
과제의 금지된 조건을 살펴봅시다.
1. libft는 위 프로젝트에서 사용이 금지되어 있습니다.
+ libft 라이브러리의 사용을 금지한 것입니다. 개별 함수는 사용할 수 있습니다.
2. lseek()은 금지되어 있습니다.
# lseek() 파일 디스크립터의 초점을 이동하는 함수 입니다. unistd헤더의 read/write 함수를 사용할 때, 첫 번째 인자로 fd값이 들어갑니다. 이 fd는 파일을 지칭하지만 내부에서는 읽거나 쓰는 바이트에 따라 텍스트내에서 위치를 이동하고 있습니다. 텍스트 파일에 "0123456789"로 10개의 숫자가 적혀있다고 가정해 봅시다. read 함수로 2글자를 읽고 write 함수로 "ABC" 3글자를 작성하면, 텍스트 파일의 내용이 "01ABC56789"로 바뀌게 됩니다. 바로 초점이 이동했기 때문에 이런 결과가 나타나게 됩니다. 더 자세한 내용은 제가 공부한 윤성우의 열혈 C 프로그래밍에 자세히 나와 있습니다.
3. 전역 변수는 금지되어 있습니다.
구현 전 사전 지식
1. read 함수의 동작 방식
2. 정적 변수의 개념과 활용
1. read() - 파일 읽기
함수원형: ssize_t read(int fd, void *buff, size_t nbytes) - unistd.h
반환 값: 읽은 바이트 수, 실패 시 -1
read함수는 fd의 내용을 nbytes만큼 읽고 buff에 저장합니다. 그리고 읽은 수를 반환합니다.
따라서 read 함수를 실행할 때마다 에러 발생(read의 반환 값이 -1)을 확인해 주어야 하며, 읽은 글자가 저장된 buff에서 한 줄의 조건에 부합한 부분을 떼어내어 반환해야 합니다.
2. Static 변수 (정적 변수)
지역 변수와 매개 변수
우리가 지금껏 사용한 지역 변수와 매개 변수는 선언 시 스택(stack) 영역에 할당됩니다. 그리고 함수를 빠져나가게 되면 소멸하는데, 메모리의 해제는 후입선출(LIFO, Last-In First-Out)의 방식으로 동작합니다. 또한 쓰레기 값을 가지고 있어 초기화를 해주어야 합니다.
정적 변수
정적 변수는 선언 시 데이터(data)영역에 할당됩니다. 그리고 프로그램이 종료되기 전까지 남아있으며, 1회 초기화됩니다. 초기화를 따로 해주지 않으면 자료형에 따라 '0' 값으로 초기화됩니다.
get_next_line 함수를 실행할 때, 읽은 문자열을 저장할 버퍼가 함수를 실행할 때마다 선언과 초기화를 반복한다면, 이전 문자열을 기억하고 있지 않아서 제대로 동작하지 못하게 됩니다. 여기서 정적 변수를 활용할 수 있습니다.
구현
Pseudo code
char *get_next_line(int fd)
{
문자열이 저장될 포인터 선언 //backup
읽을 문자열을 가리킬 포인터 선언 //buff
반환할 문자열을 가리킬 포인터 선언 //result
if 동작할 수 없는 조건을 확인한다.
널을 반환한다.
읽을 글자수 + 1 만큼 버퍼를 할당한다.
파일을 읽는 함수를 실행한다.
버퍼를 해제한다.
한 줄이 완성되었으면 반환한다.
}
char *read_file(int fd, char *backup, char *buff)
{
read의 반환 값을 확인할 변수 선언 //rbyte
메모리 해제를 위한 포인터 선언 //tptr
read반복문 시작(줄 바꿈이 발견되지 않거나 읽을 수 있는 글자가 남아있을 때)
{
read함수 실행
if rbyte의 반환값 확인
오류 처리
읽은 글자가 저장된 버퍼를 문자열로 만든다.
tptr이 backup을 가리키도록 한다.
backup에 buff를 붙인다.
이전 backup을 가리키는 문자열을 tptr을 이용해서 해제한다.
}
if 글자가 존재하지 않을 때
오류 처리
backup에서 한 줄을 완성한다.
}
char *make_line(char *backup)
{
인덱스를 표시할 변수 선언 //idx
반환 이전 문자열을 가리킬 포인터 선언 //return_to_str
메모리 해제를 위한 포인터 선언 //tptr
if backup에 줄바꿈이 존재할 때
{
return_to_str이 줄 바꿈 이전 문자열을 가리키게 한다.
backup이 현재 가리키고 있는 것을 tptr이 가리킨다.
backup이 줄 바꿈 이후 남은 문자열을 가리키게 한다.
tptr을 이용해 메모리를 해제한다.
return_to_str을 반환한다.
}
#backup은 현재 마지막 줄을 갖고있다.
backup을 반환한다.
}
동료 평가
1. 정적 변수를 설명할 수 있어야 합니다.
2. open 함수를 비롯하여 fd의 개념을 설명할 수 있어야 합니다.
3. 표준 입력으로 함수를 실행하는 방법을 알고 있어야 합니다.
4. read 함수 에러가 발생했을 때 정적 변수를 어떻게 처리하였는지 설명해야 합니다
+ 처리 방식은 디펜스의 영역입니다. 어떻게 처리하였든지 간에 자신의 방식을 정당화해야 합니다.
마치며
Bonus part는 연결리스트와 포인터배열로 풀이가 갈립니다. 두 풀이 모두 장단점이 있고 디펜스에서 자주 논의 되는 부분이므로 따로 다루겠습니다.