본문 바로가기

교육/42서울

GNL(Get Next Line) 문제 이해하기

728x90

GNL을 처음 접하고 도대체 뭘 만들라고 하는 건지, 문제에 대한 방향성을 잡기 바란다는 마음으로 이 글을 작성하였다.

 

보너스 문제를 도전하거나, 문제를 이해한 분들에게는 도움이 되지 않을 것이라고 생각한다.

 

사용한 테스트 툴

https://github.com/charMstr/GNL_lover

https://github.com/Hellio404/Get_Next_Line_Tester

https://github.com/Mazoise/42TESTERS-GNL

https://github.com/C4r4c0l3/gnl-war-machine-v2019

https://github.com/DontBreakAlex/gnlkiller

https://github.com/mrjvs/42cursus_gnl_tests

 

이중에 Get_Next_Line_Tester 테스트 툴은 과카몰리 SSH 환경에서만 동작하였다. 맥북 Big sur에서 제대로 동작하지 않았다.

 

 

GNL이 도대체 뭐지?

처음 gnl을 접하고, 어디에 쓰는건가 싶었다.

테스트 툴들의 메인(main) 함수 코드를 몇 개 살펴보니 감이 좀 왔다.

 

먼저 프로토 타입을 살펴보자.

 

int get_next_line(int fd, char **line)

 

문제에서 주어진 프로토 타입은 위와 같이 생겼다.

 

이 함수의 리턴은  -1, 0, 1 을 리턴해야 한다.

1 : A line has been read. 라인 읽음.

0 : EOF has been reached. 끝까지 읽음.

-1 : An error happened. 에러 생김.

 

 

 

int fd, char **line

 

인자 값을 보면 int fdchar **line을 넘겨준다.

 

**line을 이중 포인터로 받는 이유는 main에서 line을 char 포인터로 선언하고, 포인터의 주소 값을 전달해주었기 때문에 이중 포인터로 받아야 한다. 

 

 

그래서 이런 line을 get_next_line() 함수에서 어떻게 써야 하는가? 

12345
abcd

 

이런 형태의 text파일이 있다고 생각해보자.

"12345"뒤에는 개행(\n)이 존재할 것이다. 

line개행을 제외한, 12345라는 값을 가리키도록 하면 된다.

 

그리고 라인 하나를 읽었으니, 해당 함수는 리턴 값으로 1을 반환하면 된다.

 

 

 

그렇다면 테스트 코드에서는 get_next_line()을 어떻게 호출하는가?

int main() {
	int fd;
	int i;
	int j;
	char *line = 0;
	char *lineadress[66];

	j = 1;
	printf("\n==========================================\n");
	printf("========= TEST 2 : Empty Lines ===========\n");
	printf("==========================================\n\n");

	if (!(fd = open("/Users/epicarts/github/GNL/42TESTERS-GNL/files/empty_lines", O_RDONLY)))
	{
		printf("\nError in open\n");
		return (0);
	}
	while ((i = get_next_line(fd, &line)) > 0)
	{
		printf("|%s\n", line);
		free(line);
		j++;
	}
	printf("|%s\n", line);
	free(line);
	close(fd);

	if (i == -1)
		printf ("\nError in Fonction - Returned -1\n");
	else if (j == 9)
		printf("\nRight number of lines\n");
	else if (j != 9)
		printf("\nNot Good - Wrong Number Of Lines\n");
}

 

위 코드는 42TESTERS-GNL 테스트 코드의 일부를 변형한 것이다.

 

간단하게 살펴보면

 

1. open으로 파일을 연다.

2. open에서 리턴 받은 fd 변수와  &lineget_next_line() 함수에 넣는다.

3. get_next_line에서 리턴 받은 i로 파일의 라인을 읽었는지 파악한다. 

4. 라인이 남아있다면 printf로 line을 출력한 뒤 free로 메모리 해제한다.

5. 파일의 끝까지 읽을 때까지 반복.

6. 파일을 다 읽었으면, 안전하게 close() 해준다.

 

 

GNL 프로젝트는 open함수로 파일을 열었을 때 받는 파일 디스크럽터(fd)를 사용하여 한 줄씩 line에 저장하는 함수를 구현하면 된다고 할 수 있다. (정확한 의미로는 line의 포인터가 한 줄을 가리키게 만들어야 한다는 뜻)

 

 

open와 read만으로 파일을 읽는 방법

주어진 파일 디스크럽터를 읽는 방법으로는 read() 함수를 쓰면 된다. 

 

char buf[BUFFER_SIZE];

fd=open("file.txt",O_RDONLY);
n=read(fd,buf,BUFFER_SIZE);

 

코드가 많이 생략되긴 했지만, 일반적으로 위와 같은 방법으로  읽을 수 있다.

 

open으로 파일을 열고, read함수를 사용해 BUFFER_SIZE만큼 읽어온 다음 buf에 저장하는 방식이다. (BUFFER_SIZE는 이미 define 되어 있다, gcc 컴파일 플래그에서 -D BUFFER_SIZE=32와 같은 옵션을 주었기 때문)

 

 

 

Static 변수는 왜 쓰는가?

위 문제에서는 static 변수를 쓰라고 한다.

static으로 선언한 변수는 함수가 종료되더라도 그대로 프로그램이 종료되지 않는 이상 메모리상에 계속 남아있게 된다. 

 

이를 활용하여, seek() 함수를 쓰지 않아도, read() 함수만을 사용해 과거에 있던 값들을 저장해 놓을 수 있다.

 

간단하게 예시를 들어보자면, 

 

12345
abcd

 

BUFFER_SIZE8이라고 해보자. 

 

main함수에서 get_next_line(fd, line) 함수를 호출해보자.

이 함수가 제대로 동작을 한다면,

1. 처음 함수 호출 시 리턴으로 1을 반환할 것이고, line에는 "12345"가 담길 것이다.

2. 두 번 함수 호출 시 리턴으로 0을 반환할 것이고, line에는 "abcd"가 담길 것이다.

 

 

그렇다면 get_next_line(fd, line) 내부에서는 어떤 일이 일어날지 생각해보자.

 

1. 처음 함수 호출 시 read로 BUFFER_SIZE 만큼 파일을 읽게 되면, "12345\nab" 만큼 읽게 될 것이다. 

그리고 line에는 "12345"를 담을 것이고, 1을 리턴할 것이다.

2. 두 번 함수 호출 시  read로 BUFFER_SIZE만큼 파일을 읽게 되면...? "cd" 만큼 읽게 될 것이다.

 

여기에서 의문점이 생긴다.

 

우리가 원하는 값은 "abcd"인데, 두 번째 읽었을 때의 "cd"를 원하는 값인 "abcd"으로 만들 것인가?

이때 이러한 의문점을 사용될 수 있는 게 static 변수라고 할 수 있다.

 

이전에 값을 미리 저장하여 "ab" + "cd" = "abcd"를 할 수 있지 않을까? 

 

이게 내가 생각하는 문제에서 요구하는 바라고 생각한다.

 

 

728x90

'교육 > 42서울' 카테고리의 다른 글

42 inception 과제 기초 가이드 - 도커 컴포즈로 실제 서비스를 올려보자  (2) 2021.06.23
PUSH_SWAP 과제  (0) 2021.05.24
netwhat 문제 풀이  (0) 2021.02.20
ft_printf  (0) 2021.02.18
ft_libft 테스터기 링크 및 팁  (0) 2021.01.10