42 CURSUS / GET_NEXT_LINE

2021. 6. 16. 23:0742 SEOUL/42 cursus

GET_NEXT_LINE은 어떤 함수일까?

GET_NEXT_LINE의 프로토 타입

//	get_next_line
int get_next_line(int fd, char **line);

gnl()는 인자로 들어오는 fd(파일 디스크립터에서 개행 전까지 문자를 읽어서 함께 들어온 **line이라는 인자에 값을 담아주고,

결과값으로 

  • 한 줄을 읽어오는데에 성공했을 때 1을 리턴
  • 파일의 끝(EOF)를 만났을 때 0을 리턴
  • 읽어오는 데 실패했을 때 -1을 리턴한다.

gnl 함수는 fd에서 임의의 버퍼 사이즈만큼의 값을 읽어와서, 지금까지 읽어온 값이 들어있는 **line 변수에 개행 전까지의 값을 저장해주는 함수이다.

 

read() 함수와 파일 디스크립터

read() 함수의 프로토타입

ssize_t read(int fd, void *buff, size_t nbytes); 

해당 함수는 fd(파일 디스크립터)를 통해 nbytes만큼을 읽어 buff에 담아주는 함수이다.

여기서 size_tssize_t형의 인자가 들어오게 되는데, 이는 각각 sizeof 연산자가 반환하는 값을 담기 위해 정의된 타입으로, Singed Size와 Size를 나타내며, 보통의 32 bit 구조에서는 각각 signed int, unsigned int로 생각해도 거의 무방하다.(stdlib.h 참조)

 

read 함수는 반환값으로 

  • 읽어오는데에 성공했다면, 읽어온 바이트 수
  • 읽어오는데에 실패했다면, -1
  • 읽을 데이터가 없다면 (EOF에서 시도했을 경우), 0을 반환한다.

파일 디스크립터는 쉽게 말해 open() 함수를 이용하여 파일에 접근하게되면, 파일을 제어하기 위해 정수형의 파일 디스크립터를 부여받게 되는데, 이를 인덱스로 하여 파일에 직접 접근할 수 있다.

https://youngmon.tistory.com/3

PSEUDO로 알아보는 동작 구조

구현

int get_next_line(int fd, char **line)
{
	char *buffer;
    int i = 0;
    
	if (buffer.read_file == True){
    	if (buffer.has_nl?){
        	while (*buffer != '\n'){
            	*line[i] = bufffer[i];
                i++;
            }
        else
        	while (!(buffer.read_file) && buffer.read_file.include?'\n')
            	*line[i] = buffer[i]
                i++;
        }
    }
}

ㄷㅐ충 표현하려고 했는데, 오히려 수도가 어렵군;

아무튼 기본적인 로직은 다음과 같다.

 

메인에서 fd와 문자열 값을 반환받을 **line이라는 변수가 들어온다. 해당 문제에서는 line의 value가 필요한 것이 아닌, 해당 변수를 할당 해제하고, 할당하고, 수정해야하기 때문에 call by reference하여 해당 변수를 직접 참조할 수 있게 도와준다.

 

 또한 여러개의 파일 디스크립터가 번갈아 들어올 수 있기 때문에 버퍼가 담길 공간을 fd를 요소로 하는 정적 변수로 두어 fd를 기준으로 참조할 수 있도록 한다. (해당 방법은 OPEN_MAX를 이용한 정적 배열 또는 Linked_list를 사용하는 방법 등이 있다.)

시험 볼 때 링크드 리스트로 풀면 시간이 넘나 부족하다고 한당 ;

그래서 static 문자열 배열을 이용해서 풀어보도록 함

 

static char *buffer_list[OPEN_MAX];
//buffer_list[fd]의 형식으로 fd에 따라 다른 배열의 부분을 가져와 사용하게 됨

char *buffer;
//buffer_list[fd]에 들어가기 전 문자열을 모으고 합치는 등의 역할을 할 임시버퍼

int nl_idx;
//밑에 설명하겠지만, \n이 있을 때 해당 위치 전까지만 반환해야하기 때문에 \n의 위치를 저장할 인덱스변수

int read_bytes;
//read함수로 수를 불러올 때 값에 따라 다르게 분기하기위해 read 함수의 반환값을 저장할 변수

일단 변수는 4개를 사용했다.

 

일단 전체적으로 get_next_line의 플로우를 pseudo로 작성해봄

if (!fd.is_available? || BUFFER_SIZE <= 0 || !line ||
    !(buffer = (char *)malloc(sizeof(char) * (BUFFER_SIZE + 1))))
    return (-1);
    
// 파일 디스크립터가 비정상적인 숫자 (음수 || 가능한 수의 범위 이상)이거나 BUFFER_SIZE가 파일을 읽어올 수 없거나,
    버퍼의 메모리 할당에 실패했을 때 에러 리턴
    
while (read_bytes = read(fd, buffer, BUFFER_SIZE)
// BUFFER_SIZE만큼 값을 읽어오고 read의 반환값이 0이라면 파일의 끝을 뜻함
{
	buffer[read_bytes] = '\0';
    buf_list[fd] = strjoin(buf_list[fd], buffer);
    //buf_list[fd]에 읽어온 값을 전부 넣어줌
	if ((nl_idx = find_nl(buf_list[fd])) >= 0)
    	return (buf_list.before_nl & (buf_list = buf_list.split("\n").save) & free(buffer));
	// 만약 '\n'이 있으면 -1이 반환되고, 아니라면 해당 인덱스를 반환하게 해 두었다.
    // 전체 길이와 비교해서 같으면 개행이 없는 것으로 풀려고했다가 오히려 코드가 더러워져서 git hidaehyunlee님꺼 참고함
}

	free(buffer);
    return (all_str());

read함수가 계속해서 파일을 읽어오는데, 만약 0이라면 while문이 종료되고 버퍼를 할당 해제하고 모든 문자열을 리턴한다.

여기까지만 하고 각 함수를 구현하면 되는데, all_str에도 nl을 찾아줘야한당.

처음에 한 번 헷갈렸던 부분인데, read가 읽을 문자가 없어 0을 반환했다면, 모든 문자열을 리턴하게 되는데, 만약 이전에 문자열에 '\n'이 두개 이상 들어왔다면 마지막에 또 한 줄만 읽어와야하는데 남은 개행들까지 모두 반환하게 된다.

 

int find_nl(buffer) =>
{
	buffer.each { |x|, if x == '\n' return x.index;
}

int all_str (buffer, line, read_bytes)
{
	if find_nl >= 0
    	return (buf_list.before_nl & (buf_list = buf_list.split("\n").save) & free(buffer));
	if (buffer)
    	line = buffer;
        buffer = 0;
        // buffer가 가리키는 포인터를 0으로 보내 댕글링 포인터를 가드해준다.
        return (0);
        //파일이 끝났다는 시그널
    //buffer가 없다면?
    line = strdup("");
    return (0);
    // 이전에 BUFFER_SIZE에 딱 맞아 떨어지게 나왔다면 해제 가능한 빈 문자열을 반환
}

 

다음으로 가장 중요한 역할을 하는 split("\n") 역할을 해줄 함수 ..!

int split_nl(**buffer, **line, nl_idx)
{
	(*buffer)[nl_idx] = '\0';
    //'\n'이 있던 위치에 널 문자를 넣어 가공하기 쉽게 해준다.
    *line = strdup(*buffer);
    //널 문자열 전까지 복사가 된다.
    
    
    if (!strlen(*buffer + nl_idx + 1))
    //개행 문자 뒤에 문자가 없다면
    {
    	free(buffer);
        buffer = 0;
        return (1);
    }
    char *tmp = strdup(*buffer + nl_idx + 1);
    free (*buffer);
    *buffer = tmp;
    // 임시 변수에 널 뒤부터의 문자열을 복사해준 후 포인터가 가리키는 위치를 다시 잡아줌
	return (1);
}

 

이렇게하여 나는 프엪으로 넘어갈갱 ..

테스터도 통과된당,,

사실 링크드 리스트로 다 풀어놓고 Leaks 잡는 와중에 본과정 피씬 17일 갔다오니깐 내가 짜둔 코드가 이해가 안가서 다시 짜다가 현타와서 OPEN_MAX로 풀었따 ,,

내용과는 상관없긴한데 고급 언어도 재밌긴하다 솔직히 이런게 기본 함수 아니면 금방 구현하니깐,, 

암튼 끗

 

코드를 짠 지도 오래 되었고, 그림이 너무 더럽게 나와버렸다 ,,

일단 차트를 설명하자면, 파일 디스크립터에 문제가 있거나, 인자로 들어와 문자열을 받을 line에 문제가 있거나, 메모리 할당에 실패하면 -1을 먼저 리턴한다.

그리고 파일 디스크립터의 파일을 읽어서 static 버퍼에 strjoin으로 전부 더해준다,

그런 후 지금까지 읽어온 문자열에 \n가 있다면 해당 '\n'문자를 '\0'으로 바꾸고 *line = strdup(buf_list[fd])으로 주어 개행 문자 전까지의 문자를 복사해 내보내고, buf_list는 개행 문자 뒤부터 다시 복사해 준다.

 

read로 문자열을 읽어오는 도중 EOF를 만나 더 이상 읽을 문자가 없을 경우에는 반복문이 종료되고 buffer는 비어있는 상태이다. buf_list[fd]를 반환해 주면서, 이미 읽어온 문자열에도 개행이 여러 번 들어갔었을 수 있으므로 '\n'을 찾아 개행 문자가 있다면 다시 front_ret 함수로 넘기고, 아니라면 해당 문자열을 line에 담아주고, 1을 반환한다.

만약 읽어온 값이 없고, buf_list[fd]에 남은 값도 없으며, buffer가 비어있다면 strdup("")로 빈 해제 가능한 문자열을 반환하고 1을 반환한다.

반응형

'42 SEOUL > 42 cursus' 카테고리의 다른 글