100%를 한번에 바꾸는건 어려워도 1%를 100번 바꾸는건 쉽다.

생각정리 자세히보기

42 Seoul

[42 Seoul] Get Next Line

dc-choi 2022. 3. 7. 19:23
반응형

Get Next Line

fd에서 한 줄을 읽는다는 것은 너무나도 지루한 일입니다.

 

요약: 이 프로젝트의 목적은 파일 디스크립터로부터 읽혀진, 개행으로 끝나는 한 줄을 반환하는 함수를 코드화 하는 것입니다.

 

알아야할 선수지식

파일스크립터

파일 디스크립터란 리눅스 시스템에서 프로세스가 파일을 다룰 때 사용하는 개념으로 프로세스에서 특정 파일에 접근할 때 사용하는 추상적인 값이다. 파일 디스크립터는 일반적으로 정수값을 가진다.

 

흔히, 리눅스 시스템에서 모든 것을 파일이라고 한다. 우리가 흔히 생각하는 파일부터 디렉토리, 소켓, 드라이버, 블록 디바이스, 등등 모든 객체를 파일로 관리한다.

 

프로그램이 프로세스로 메모리에서 실행될 때, 기본적으로 할당되는 파일 디스크립터는 표준 입력, 표준 출력, 표준 에러이며, 이들에게 각각 0, 1, 2라는 정수가 할당된다.

 

프로세스가 실행 중에 파일을 열면 커널은 해당 프로세스의 파일 디스크립터 숫자 중 0, 1, 2를 제외한 사용하지 않는 가장 작은 값을 할당해준다. 그 다음 프로세스가 열려있는 파일에 시스템 콜을 이용해서 접근할 때, 파일 디스크립터 값을 이용해서 파일을 지칭할 수 있다.

 

FD의 최대값은 OPEN_MAX라는 값이다.

즉, 하나의 프로세스 당 최대 OPEN_MAX개의 파일을 열 수 있다. OPEN_MAX 값은 플랫폼에 따라 다르다.

최대 파일 갯수는 다음 명령어를 통해 확인이 가능하다.

getconf OPEN_MAX

 

정적변수

정적변수란 프로그래밍에서 정적으로 할당된 변수이며, 그 수명은 프로그램의 전체 실행임을 의미한다.

 

변수 수명은 변수를 사용할 수 있는 범위와 대조됩니다. 흔히 전역과 지역은 수명을 의미하는 것이 아니라 범위를 가리킵니다.

 

범위 측면에서 정적 변수는 프로그램의 전체 실행 범위를 갖지만 범위가 더 제한적일 수 있습니다.

 

기본적인 구분은 전역 범위를 가진 정적 전역 변수와 정적 로컬 변수입니다.

 

정적 로컬 변수는 정적 로컬 변수가 존재하는 함수가 호출되는 횟수에 상관없이 한 번만 초기화 되며, 정적 로컬 변수는 선언된 함수에 대한 많은 호출을 통해 그 값이 유지되고 접근 가능하기 때문에 로컬 변수와 다릅니다.

 

read()

파일의 내용을 읽는 함수입니다.

헤더 unistd.h
형태 ssize_t read(int fd, void *buf, size_t bytes)
인수 int fd => 파일 디스크립터
void *buf => 파일을 읽어 들일 버퍼
size_t bytes => 버퍼의 크기
반환 ssize_t == -1 => 실패
ssize_t == 0 => EOF

ssize_t > 0 => 읽어들인 바이트 수 정상

 

gcc -D

매크로를 외부에서 선언할 때 사용하는 gcc 옵션

 

Goals

이 프로젝트는 당신의 콜렉션(아마 라이브러리)에 아주 편리한 함수를 추가하게 할 뿐만 아니라, C 프로그래밍에 있어서 아주 흥미롭고 새로운 개념인 '정적 변수'를 배울 수 있도록 도울 것입니다.

 

Common Instructions

프로젝트는 Norm 규칙에 맞춰 작성되어야 합니다. 보너스 파일/함수가 존재할 경우, 그 또한 norm 검사에 포함되며 norm error가 있을 시 0점을 받게 됩니다.

 

함수들은 정의되지 않은 행동들과 별개로 예기치 않게 중단되어서는 안 됩니다.(예를 들어, segmentation fault, bus error, double free 등) 만약 이렇게 중단되면, 당신의 프로젝트는 작동하지 않는 것으로 여겨지고 평가에서 0점을 받을 것입니다.

 

필요한 경우 heap에 할당된 모든 메모리 공간은 적절하게 해제되어야 합니다. 메모리 누수는 용납될 수 없습니다.

 

과제에서 필요한 경우, -Wall -Wextra -Werror 플래그를 지정하여 컴파일을 수행하는 Makefile을 제출해야 합니다. Makefile은 relink 되어서는 안 됩니다.

 

Makefile은 최소한 $(NAME), all, clean, fclean, re 규칙을 포함해야 합니다.

 

프로젝트에 보너스를 제출하려면, Makefile에 보너스 규칙을 포함해야 합니다. 이 보너스 규칙은 프로젝트의 메인 파트에서 금지되었던 모든 다양한 헤더, 라이브러리, 또는 함수들을 추가하여야 합니다. 보너스 과제는 반드시 _bonus.{c/h}라는 별도의 파일에 있어야 합니다. 반드시 수행하여야 하는 메인 파트의 평가와 보너스 파트의 평가는 별도로 이뤄집니다.

 

만일 프로젝트에서 여러분의 libft 사용을 허용한다면, 소스들과 관련 Makefile을 함께 루트 폴더 안에 있는 libft 폴더에 복사해야 합니다. 프로젝트의 Makefile은 우선 libft의 Makefile을 사용하여 라이브러리를 컴파일한 다음, 프로젝트를 컴파일해야 합니다.

 

이 과제물을 제출할 필요가 없고, 채점 받을 필요가 없을지라도, 저희는 여러분들이 프로젝트를 위한 테스트 프로그램을 만들 것을 권장합니다. 이것은 여러분의 과제물과 동료들의 과제물을 쉽게 테스트할 수 있게 도울 것입니다. 또한, 평가를 진행할 때 이러한 테스트 프로그램들이 특히 유용하다는 사실을 알게 될 것입니다. 평가 시에는 여러분의 테스트 프로그램과 평가 받는 동료의 테스트 프로그램들을 당연히 자유롭게 사용할 수 있습니다.

 

할당된 git 저장소에 과제물을 제출하세요. 오직 git 저장소에 있는 과제물만 등급이 매겨질 것입니다. Deepthought가 평가하는 과제의 경우엔, 동료평가 이후에 Deepthought가 수행됩니다. 만약 Deepthought 평가 중에 오류가 발생한다면, 그 즉시 평가는 중지될 것입니다.

 

Mandatory part

프로토타입 char *get_next_line(int fd);
제출할 파일 get_next_line.c, get_next_line_utils.c, get_next_line.h
매개변수 읽어들일 파일의 디스크립터 (서술자)
반환값 읽혀진 라인 : 한 줄이 제대로 읽힘
NULL : 읽을 라인이 더이상 없거나 에러 발생
사용가능한
외부 함수
read, malloc, free
설명 파일 디스크립터로부터 한 줄을 읽고,
반환하는 함수를 작성하시오.

 

get_next_line 함수를 반복문 안에서 호출하면, 파일 디스크립터에 존재하는 텍스트를 EOF 전까지 한 번에 한 줄씩 읽어들일 수 있습니다.

 

여러분의 함수는 방금 읽어들인 문자열 한 줄을 반환하여야 합니다. 더이상 읽어올 것이 없거나 에러가 발생하면 NULL을 반환하여야 합니다.

 

파일에서 읽을 때, 표준입력으로부터 읽어들일 때 모두 함수가 제대로 동작하는지 확인하십시오.

 

당신의 프로그램은 -D BUFFER_SIZE=xx 플래그를 이용하여 컴파일 되어야 합니다. 이것은 여러분의 get_next_line에서 read함수를 호출하기 위한 buffer size로 사용될 것이며, Moulinette와 평가자가 임의로 값을 수정하여 테스트할 것입니다.

 

컴파일은 이런 식으로 진행됩니다 :
gcc -Wall -Wextra -Werror -D BUFFER_SIZE=42 <파일들>.c

 

read 함수를 사용할 때에는, 파일 또는 표준 입력으로부터 값을 읽어들이기 위해 컴파일 시에 정의되는 BUFFER_SIZE를 사용해야 합니다. 평가 시에, 함수 테스트를 위하여 값이 변동될 수 있습니다.

 

get_next_line.h (헤더 파일)에는 적어도 get_next_line 함수의 프로토타입이 존재하여야 합니다.

 

BUFFER_SIZE 값이 9999인 경우에도 함수는 여전히 작동하나요? BUFFER_SIZE 값이 1이라면? 10000000이라면? 왜 그런지 아시나요?

 

get_next_line이 호출될 때마다 가능한 한 적게 읽어들이도록 해야 합니다. 만약 newline을 만나면, 현재 라인을 반환해야 합니다. 전체 파일을 읽어들인 다음에 한줄씩 처리하려 하지 마세요.

 

테스트하지 않고 프로젝트를 제출하지 마세요. 여러분의 함수를 위해 돌려볼 수 있는 테스트는 많습니다. 파일로부터, redirection으로부터, stdin으로부터의 읽기를 시도해 보세요. 표준 출력에 newline을 보낼 때 프로그램은 어떻게 동작하나요? CTRL-D는요

 

lseek은 허용된 함수가 아닙니다. 파일 읽기는 오로지 한번만 행해져야 합니다.

 

만약 동일한 파일 디스크립터의 두 호출 사이에서, 첫 번째 fd에서 EOF에 도달하기 전에 다른 파일로 전환될 경우, 우리는 get_next_line이 정의되지 않은 동작을 가진다고 생각합니다.

 

마지막으로 get_next_line은 바이너리 파일을 읽을 때 정의되지 않은 동작을 가진다고 생각하셔야 합니다. 그러나 여러분이 원한다면 이러한 동작을 논리적으로 구현하셔도 됩니다.

 

전역 변수는 금지되어 있습니다.

 

중요: 'eof에 도달하였고 \n이 존재하지 않을 때'를 제외하고, 함수가 반환하는 문자열 한 줄에는 \n이 포함되어야 합니다.

 

정적 변수가 무엇인지 익혀 두면 좋은 시작이 될 겁니다 :)

 

Bonus part

get_next_line 프로젝트는 간단하기 때문에 보너스를 받을 만한 여지가 거의 없지만, 우리는 여러분의 상상력이 풍부하다고 확신합니다. 만약 여러분이 필수적으로 해야하는 부분들을 완벽하게 숙달했다면, 어떻게든 보너스 파트를 완성하고 더 앞으로 나아가세요. 다시 말하지만, 필수적으로 해야하는 부분들이 완벽하지 않다면, 보너스는 고려되지 않을 것입니다.

 

이 파트를 위해서는 기존의 3개 파일에 _bonus를 붙여서 제출하세요.

 

하나의 정적변수로 get_next_line 성공하기.

 

get_next_line을 사용하여 여러 개의 파일 디스크립터를 관리할 수 있는 것. 예를 들어, 파일 디스크립터 3, 4, 5에 접근 가능한 경우, get_next_line은 각 디스크립터의 리딩 스레드를 잃지 않은 채로 3에서 한 번, 4에서 한 번, 다시 3에서 한 번, 5에서 한 번 호출될 수 있습니다.

 

구현 설명

char *get_next_line(int fd)

char	*get_next_line(int fd)
{
	// getconf OPEN_MAX로 가져온 OPEN_MAX가 256이라 257까지 선언
	static char	*backup[257]; // 읽어온 파일을 저장하는 정적변수
	char		*buffer; // read()로 BUFFER_SIZE만큼 받아올 버퍼
	char		*result; // 결과값
	
	// fd가 0 ~ 256을 벗어나거나 BUFFER_SIZE가 0보다 작거나 같을 경우 NULL 반환
	if (fd < 0 || fd > 256 || BUFFER_SIZE <= 0)
		return (NULL);
 	// 버퍼를 BUFFER_SIZE만큼 동적 할당
	buffer = (char *)malloc(sizeof(char) * BUFFER_SIZE + 1);
	if (!buffer)
		return (NULL);
	// 정적변수 동적할당
	if (!backup[fd])
		backup[fd] = ft_strdup("");
	// 파일을 버퍼만큼 읽는다
	result = get_line(fd, &buffer, &backup[fd]);
	free(buffer);
	return (result);
}

static char *get_line(int fd, char **buffer, char **backup)

static char	*get_line(int fd, char **buffer, char **backup)
{
	int		byte; // read_file()로 파일을 잘 읽어왔는지 확인하는 변수
	char	*last_line; // 결과값
    
	// 파일을 read()를 이용해 읽어온다.
	byte = read_file(fd, buffer, backup);
	// 파일을 읽어오지 못했을때 예외처리
	if (!**backup || byte == -1)
	{
		free_ptr(backup);
		return (NULL);
	}
	// *backup에 \n을 발견했을 경우 backup에 첫번째 \n까지 문자열 반환후 반환 문자 삭제
	if (ft_strchr(*backup, '\n'))
		return (separate_line(backup));
	// 읽어들인 마지막줄까지의 backup을 동적 할당
	last_line = ft_strdup(*backup);
	free_ptr(backup);
	return (last_line);
}

static int read_file(int fd, char **buffer, char **backup)

static int	read_file(int fd, char **buffer, char **backup)
{
	int		byte; // read()로 파일을 잘 읽어왔는지 확인하는 변수
	char	*old_backup; // 버퍼로 읽은만큼 저장하기 위한 변수

	byte = 1;
	// \n을 발견전까지 BUFFER_SIZE만큼 파일을 읽음.
	while (!ft_strchr(*backup, '\n') && byte)
	{
		byte = read(fd, *buffer, BUFFER_SIZE);
		if (byte == -1)
			return (byte);
		(*buffer)[byte] = '\0';
		// 버퍼를 읽은 만큼 백업에 넣는다.
		old_backup = *backup;
		*backup = ft_strjoin(old_backup, *buffer);
		free(old_backup);
	}
	return (byte);
}

static char *separate_line(char **backup)

static char	*separate_line(char **backup)
{
	int		idx; // \n의 위치를 찾기 위한 인덱스
	char	*line; // \n 전까지 문자열
	char	*old_backup; // line을 구하기 위한 변수

	idx = 0;
	while ((*backup)[idx] != '\n')
		idx++;
	old_backup = *backup;
	// \n의 그 전부분 저장
	line = ft_substr(old_backup, 0, idx + 1);
	// \n을 만난 그 뒷부분을 저장
	*backup = ft_strdup(&(*backup)[idx + 1]);
	free(old_backup);
	return (line);
}

static void free_ptr(char **ptr)

static void	free_ptr(char **ptr)
{
	free(*ptr);
	*ptr = NULL;
}

 

반응형

'42 Seoul' 카테고리의 다른 글

[42 Seoul] Born2BeRoot 설정가이드(Debian)  (0) 2022.05.13
[42 Seoul] Born2BeRoot 설치가이드(Debian)  (0) 2022.05.12
[42 Seoul] Born2beRoot  (0) 2022.05.09
[42 Seoul] Printf  (0) 2022.04.22
[42 Seoul] Libft  (0) 2022.01.07