📚 /42cursus

1. 소개


Reading a line on a fd is way too tedious

42서울 본과정 입과 후 세번째로 수행한 과제로, 파일 디스크립터로부터 읽혀진 - 개행으로 끝나는 한 줄을 반환하는 함수를 만드는 과제이다. 즉 파일 하나를 연 다음, 파일 내용에서 개행으로 끝나는 줄 하나를 반환해야한다. 파일을 열고, 지정된 BUFFER_SIZE 만큼 read함수를 통해 파일을 읽다가 개행문자 \n이 나타나면 개행문자 전까지만 반환한다.



2. get_next_line 명세서




3. 개념 정리


3-1. 연결리스트와 OPEN_MAX

본 과제를 BONUS까지 수행한다면, 즉 동시에 여러 파일 디스크립터를 감안하는 프로그램을 만들고 싶다면 크게 두가지 방법이 존재한다.

연결리스트로 구현

연결리스트 구조를 통해 생성되는 파일 디스크립터를 담은 노드를 계속 뒤에 이어 붙이며 관리

OPEN_MAX로 구현

해당 시스템에서 용인하는 최대 파일 디스크립터 수를 정의한 OPEN_MAX를 통해 정적 배열로 관리

평가 기준에 다른 카뎃들의 여러가지 논의가 있는 것은 맞으나, 평가자를 납득시킬 수 있는 설명(또는 노력)을 통해 원하는 방법으로 보너스까지 노려보자.


3-2. OPEN_MAX

나는 OPEN_MAX를 통해 보너스를 구현했다.

그 이유는 본 과제에서 중요하게 배워야하는 것은 파일 디스크립터라는 생소한 개념과 파일을 열고 read하고 닫고, 동적할당한 메모리를 적절하게 해제하는 것이라고 생각하기 때문이다. 연결리스트로 분명히 구현은 가능하지만, 굳이 공수를 더 들일 필요 또한 없다고 생각했다.

OPEN_MAX의 경우 한계점이 존재하는 것은 사실이다. limits.h에 정의된 OPEN_MAX는 단순 정의일 뿐이고, 리눅스 버전에 따라 다르기도 하다. 또한 컴퓨터 환경에 따라 달라지는 실질적인 파일 디스크립터 수를 반영하지 못하며 bash 환경과 vscode의 터미널 상에서 파일 디스크립터 값을 확인했을 때 차이가 존재하는 것을 알 수 있다.

그렇지만 최종적으로 이 과제에서 더 중점으로 다뤄야 하는 것은 따로 있다고 생각하기 때문에 OPEN_MAX로 구현하더라도 그 한계지점과 그 한계지점을 찾기 위한 본인의 노력을 평가 시에 함께 어필한다면 충분히 통과할 수 있다고 생각한다.(개인적인 기준)


3-3. 동적할당과 해제

메모리 누수를 막기위해서 동적으로 할당한 힙 영역의 메모리는 사용이 끝난 경우 해제해야 하는 것이 당연하다. 이때 아래 함수를 통해서 free를 조금 더 안전하고 유용하게 사용할 수 있다.

void	ft_free(void **target)
{
	if (target != NULL && (*target) != NULL)
	{
		free(*target);
		*target = NULL;
	}
}

메모리 해제 후 포인터를 NULL로 설정함으로써, 포인터의 Dangling Pointer 상태(해제된 메모리를 가르키는 상태)를 방지한다. 또한 이 과정을 통해 이미 할당 해제된 메모리 주소에 다시 접근하는 것을 방지할 수 있다.


4. Mandatory



step 01

개행문자 \n을 만날 때까지 또는 EOF에 도달할 때까지 BUFFER_SIZE 만큼 읽어나간다.

step 02

개행문자 전까지만 따로 분리해서 return을 준비한다.

step 03

추후 BUFFER_SIZE 실행을 위해서 main_buf에 나머지 남은 문자열을 저장한다.


4-1. 구현

get_next_line

char	*get_next_line(int fd)
{
	static char	*main_buf;
	char		*line;

	line = NULL;
	if (fd < 0 || BUFFER_SIZE <= 0)
		return (NULL);
	main_buf = read_line_fd(fd, main_buf); // step 01
	if (!main_buf)
		return (NULL);
	line = extract_line(main_buf); // step 02
	if (!line)
		return (ft_free(&main_buf));
	main_buf = ready_main_buf(main_buf); // step 03
	return (line);
}

main_buf 변수는 static 변수이기 때문에, 프로그램 실행 시 단 한 번 초기화 되며, 이후에 get_next_line 함수가 호출되어도 초기화되지 않고 이전에 저장된 값을 유지한다.

read_line_fd를 통해 개행 또는 EOF 전까지 BUFFER_SIZE만큼씩 파일을 읽는다.

읽은 내용은 main_buf에 저장되며, extrac_line 함수를 통해 개행 전까지 잘라 line에 담는다.

read_main_buf를 통해 main_buf를 정리(잘라낸 line을 없애고 나머지만 남김)하여 다음 호출을 대비한다.



5. BONUS


본 과제를 BONUS까지 수행한다면, 위에서 언급한 것 처럼 우리가 만든 get_next_line이 여러 파일 디스트립터를 관리할 수 있어야 한다. 쉽게 말하면, a.txt를 읽다가 갑자기 b.txt을 읽을 수 있어야 한다는 것! 다시 a.txt로 읽기 위해 돌아갔을 때, 그 전에 어디까지 읽었는지 당연히 기억하고 있어야 한다.

아래는 OPEN_MAX를 통해 디스크립터 테이블만큼 배열을 만들어 정적 배열을 통해 여러 파일 디스크립터를 다루는 방법이다.


5-1. 구현

char	*get_next_line(int fd)
{
	static char	*main_buf[OPEN_MAX];
	char		*line;

	line = NULL;
	if (fd < 0 || BUFFER_SIZE <= 0)
		return (NULL);
	main_buf[fd] = read_line_fd(fd, main_buf[fd]);
	if (!main_buf[fd])
		return (NULL);
	line = extract_line(main_buf[fd]);
	if (!line)
		return (ft_free(&(main_buf[fd])));
	main_buf[fd] = ready_main_buf(main_buf[fd]);
	return (line);
}


6. Reference