[42] GNL 구현하기

KURTY·2022년 8월 29일
0

42_SEOUL

목록 보기
2/9

get_next_line의 용도

  • file descriptor을 통해서 텍스트에서 eof까지 한 라인을 읽어 변환하는 함수

(’\n’을 기준으로, ‘\n’이 나오기 전까지의 문자열을 line에 할당)

  • 보너스 파트의 경우 한개의 static variable만을 이용해 여러 쓰레드를 이용할 수 있게 한다.

즉, 여러 개의 파일 디스크립터를 통해 여러 파일의 라인을 각각 읽을 수 있도록 한다.

파일 디스크립터(fd)

  • 운영체제가 파일 또는 하드웨어와 통신을 하기 위해 부여하는 숫자
  • 파일 디스크립터는 0, 1, 2 순으로 숫자를 부여하며, 0, 1, 2는 이미 사용중이라서 3부터 파일디스크립터를 부여한다.
    • 0 : 표준입력
    • 1 : 표준 출력
    • 2 : 표준 에러
  • open() 함수는 파일을 열고 파일 디스크립터 값을 반환한다. 그 값을 이용하여 이후에도 계속해서 그 열었던 파일에 접근 할 수 있다.

read()함수

  • 인자로 받은 bytes의 수 만큼 fd를 읽어 buf에 저장하는 함수다.
  • 읽어 온 바이트 수를 반환하며, 실패시 -1을 반환한다.
  • 파일을 끝까지 읽었다면, 다음 번에는 더 이상 읽을 바이트가 없기 떄문에 0을 반환한다.

static 변수 (정적 변수)

  • 변수를 선언할 때 static 키워드를 붙여 선언한다.
  • 메모리의 데이터 영역에 저장되어 프로그램이 종료될 때 까지 남아있는 변수다.
  • 함수를 벗어나도 해당 변수는 사라지지 않고 계속 유지된다. 하지만 함수 내부에서 선언되었다면, 다른 함수에서는 이 값을 참조할 수 없다. 또 함수의 시작 시 할당 되며, 프로그램이 종료될 때 해제된다.
  • 함수 내에서 static int num = 0 식으로 초기화 하면 프로그램이 시작될 때 변수를 초기화하며, 함수가 호출될 떄는 변수를 초기화 하지 않는다. (여러번 함수를 실행하더라도 그 변수가 또 초기화 되지 않는다)
  • 정적 변수는 초기값을 지정하지 않으면 0으로 초기화 된다.
  • 이번 과제에서는, 다음 line을 읽을 주소를 계속 저장할 수 있도록 백업 버퍼를 만들어 static변수로 선언해야 한다
  • 정적 변수를 전역 변수로 사용한다면 파일 외부에서는 사용할 수 없다.

gcc의 -d 플래그

  • 프로그램 외부에서 #define을 정의하여 컴파일 시 반영할 수 있다.
  • 이번 과제에서, 채점 시 컴파일은 다음과 같이 진행한다.

(gcc -Wall -Wextra -Werror -D BUFFER_SIZE=42 get_next_line.c get_next_line_utils.c

  • 즉 컴파일할 때 BUFFER_SIZE를 정하고 이 버퍼의 크기만큼 파일을 한 번에 읽게된다.

과제의 목표

  • GNL함수를 반복문 안에서 호출하면, fd의 텍스트를 EOF가 올 때까지 한 번에 한 줄씩 읽을 수 있다.
  • GNL함수를 처음 호출했을 때 지정한 버퍼의 크기가 매우 커서 한번에 파일을 읽었다 하더라도, 두 번째 호출했을 떄는 두번째 줄부터 읽기를 시작해야 한다.
  • file, redirection, stdin으로부터 읽었을 떄 함수가 제대로 작동해야 한다.
  • gcc -d플래그로 받은 BUFFER_SIZE가 1일때도, 9999일떄도, 1000000일 떄도 함수가 제대로 작동해야 한다.

GNL simple example (BUFFER_SIZE=4)

abcde\n

12345\0

< Get Next Line 첫 호출, 1 Cycle>

File : a b c d e \n 1 2 3 4 5 \0

Buffer : abcd e\n12 \0

Result : a b c d e \n \0

Backup : 1 2

BUFFER_SIZE = 4;

File : files에 “abide\n12345\0” 데이터가 존재

Buffer : read_and_restore함수를 이용하여 File에 있는 문자열을 Buffer에 \n or \0(EOL)를 찾을때까지 BUFFER_SIZE가 4이므로 4 Bytes씩 복사를 한다. (Buffer 마지막에는 \0으로 처리)

  • abcd : \n or \0 못찾음
  • e\n12 : \n 찾음
  • abcde\n12\0반환

Result : get_line 함수를 이용하여 Buffer의 문자열을 \n or \0 을 끝으로 하는 문자열로 정리하여 원하는 결과값으로 만든다.

  • abcde\n\0 반환

Backup : backup_next함수를 이용하여 처리된 Result 값에서 남은 문자열을 정적 변수에 저장하여 백업을 한다.

  • 다시 “get_next_line”을 호출하면 fd의 마지막 기록을 확인하고 읽지 않은 문자열부터 잃는다.
  • 백업을 위해 정적변수 사용

<Get Next Line 두 번째 호출, 2 Cycle>

File : 3 4 5 \0

Buffer : 12(backup) 345 \0

Result : 1 2 3 4 5 \0

Backup : free

BUFFER_SIZE = 4;

File : files을 GNL로 다시 오픈하면 fd 기록을 확인하고 읽지 않은 문자열 부터 확인한다.

Buffer : read_and_restore을 이용하여 File에 있는 문자열을 Buffer에 \n or \0(EOL)를 찾을때까지 BUFFER_SIZE가 4이므로 4 Byte씩 복사를 한다. (Buffer 마지막에는 \0으로 처리)

  • 12 : Backup 데이터에 붙여서 Buffer에 저장
  • 345\0 : \0찾음
  • 12345\0 반환

Result : get_line함수를 이용하여 Buffer의 문자열을 \n or \0을 끝으로 하는 문자열로 정리하여 원하는 결과값을 만든다

Backup : File의 EOL을 확인하여 backup_next함수에서 할당했던 backup변수가 free됨

Bonus

  • OPEN_MAX
    • getconf OPEN_MAX 사용 시 최대로 열 수 있는 파일의 개수가 출력됨
    • OPEN_MAX이후의 fd 값에 대해서 예외처리 사용

get_next_line.h

#ifndef GET_NEXT_LINE_H
# define GET_NEXT_LINE_H

# include <unistd.h>
# include <stdlib.h>

# ifndef BUFFER_SIZE
#  define BUFFER_SIZE 42
# endif

char	*get_next_line(int fd);
char	*get_line(char *backup);
char	*backup_next(char *backup);
char	*read_and_store(int fd, char *backup);

char	*ft_strchr(const char *s, int c);
size_t	ft_strlen(const char *str);
size_t	ft_strlcat(char *dst, const char *src, size_t dstsize);
size_t	ft_strlcpy(char *dst, const char *src, size_t dstsize);

#endif

get_next_line.c

#include "get_next_line.h"

char	*store_buf(char *buf, char *backup)
{
	char	*new_backup;
	int		new_len;

	if (!backup)
	{
		backup = (char *)malloc(sizeof(char)); // 최초 backup은 빈공간이므로 1칸 할당
		backup[0] = '\0'; // 널문자 입력
	}
	if (!backup)
		return (NULL);
	new_len = ft_strlen(buf) + ft_strlen(backup);
	new_backup = (char *)malloc(sizeof(char) * (new_len + 1));
	if (!new_backup)
	{
		free(backup);
		return (NULL);
	}
	ft_strlcpy(new_backup, backup, new_len + 1); // 복사
	ft_strlcat(new_backup, buf, new_len + 1); // 붙여넣기
	free(backup);
	return (new_backup);
}

char	*read_and_store(int fd, char *backup)
{
	char	*buf;
	int		rbytes;

	buf = (char *)malloc(sizeof(char) * (BUFFER_SIZE + 1)); 
// buf에 BUFFER_SIZE+1 만큼의 공간 할당
	if (!buf)
		return (NULL); // 널가드
	rbytes = 1; // rbytes는 while문을 돌리기 위해서 1부터
	while (rbytes != 0)
	{
		rbytes = read(fd, buf, BUFFER_SIZE); // 해당 fd를 사용해 BUFFER_SIZE 만큼 읽어옴
		if (rbytes == -1) // 읽어오지 못했을 시 free 후 NULL 반환
		{
			free(buf);
			free(backup);
			return (NULL);
		}
		buf[rbytes] = '\0'; // 널문자 
		backup = store_buf(backup, buf); // 원래의 backup에 buf를 붙임
		if (ft_strchr(buf, '\n')) // 개행문자를 발견하면 while문 break
			break ;
	}
	free(buf); // buff 메모리 해제
	return (backup); // backup반환
}

char	*get_line(char *backup)
{
	char	*buf;
	int		i;

	i = 0;
	if (!backup[0]) // backup이 빈 문자열이라면 NULL 반환
		return (NULL);
	while (backup[i] && backup[i] != '\n') // backup이 개행문자가 올때까지 i++
		i++;
	buf = (char *)malloc(sizeof(char) * (i + 2)); 
// 원래의 backup의 개행문자를 제외한 길이 + 2만큼의 공간 할당
	if (!buf)
		return (NULL); // 널가드
	i = 0;
	while (backup[i] && backup[i] != '\n')
	{
		buf[i] = backup[i]; // 원래의 backup 복붙
		i++;
	}
	if (backup[i] == '\n') // 개행문자가 나온다면
		buf[i++] = '\n'; // 개행문자 넣고
	buf[i] = '\0'; // 널문자 대입
	return (buf); // buf반환
}

char	*backup_next(char *backup)
{
	char	*new_backup;
	int		i;
	int		j;

	i = 0;
	while (backup[i] && backup[i] != '\n') // backup의 개행문자가 나올 때 까지 i++
		i++;
	if (!backup[i]) // 개행문자가 없다면
	{
		free(backup); // 메모리 해제
		return (NULL); // NULL 반환
	}
	new_backup = (char *)malloc(sizeof(char) * (ft_strlen(backup) - i)); 
// backup에서 처리가 된 문자들을 빼고 널문자의 길이를 더한 공간 할당
if (!new_backup)
	{
		free(backup);
		return (NULL); // 널가드
	}
	i++; // backup에 있는 \n 건너뜀
	j = 0;
	while (backup[i])
		buf[j++] = backup[i++]; // backup에서 처리가 되지 않은 문자들을 복사
	new_backup[j] = '\0';
	free(backup);
	return (new_backup); // 새로운 backup 최신화
}

char	*get_next_line(int fd)
{
	char		*buf;
	static char	*backup;

	if (fd < 0 || BUFFER_SIZE <= 0)
		return (NULL);
	backup = read_and_store(fd, backup); // backup 최신화
	if (!backup)
		return (NULL); // 널가드
	buf = get_line(backup); // 널문자 포함 문자열 반환
	backup = backup_next(backup);
	return (buf);
}

get_next_line_utils.c

#include "get_next_line.h"

// 문자열 내에서 문자가 있는지 확인
har	*ft_strchr(const char *s, int c)
{
	size_t	i;
	size_t	len;

	i = 0;
	len = ft_strlen(s);
	while (i <= len)
	{
		if (s[i] == (unsigned char)c)
			return ((char *)(s + i));
		i++;
	}
	return (0);
}

// 문자열 길이 반환
size_t	ft_strlen(const char *s)
{
	size_t	i;

	i = 0;
	while (s[i])
		i++;
	return (i);
}

// 문자열 붙여넣기
size_t	ft_strlcat(char *dst, const char *src, size_t dstsize)
{
	size_t	i;
	size_t	dst_len;
	size_t	src_len;

	dst_len = ft_strlen(dst);
	src_len = ft_strlen(src);
	i = 0;
	if (dstsize < dst_len + 1)
		return (dstsize + src_len);
	if (dstsize > dst_len + 1)
	{
		while (src[i] && dst_len + i + 1 < dstsize)
		{
			dst[dst_len + i] = src[i];
			i++;
		}
	}
	dst[dst_len + i] = '\0';
	return (dst_len + src_len);
}

// 문자열 복사
size_t	ft_strlcpy(char *dst, const char *src, size_t dstsize)
{
	size_t	srcsize;
	size_t	i;

	srcsize = ft_strlen(src);
	i = 0;
	if (!dst || !src)
		return (0);
	if (dstsize == 0)
		return (srcsize);
	while (src[i] && i + 1 < dstsize)
	{
		dst[i] = src[i];
		i++;
	}
	dst[i] = '\0';
	return (srcsize);
}

get_next_line_bonus.h

#ifndef GET_NEXT_LINE_BONUS_H
# define GET_NEXT_LINE_BONUS_H

# include <unistd.h>
# include <stdlib.h>

# ifndef BUFFER_SIZE
#  define BUFFSE_SIZE 42
# endif

char	*get_next_line(int fd);
char	*get_line(char *backup);
char	*backup_next(char *backup);
char	*read_and_store(int fd, char *backup);

char	*ft_strchr(const char *s, int c);
size_t	ft_strlen(const char *str);
size_t	ft_strlcat(char *dst, const char *src, size_t dstsize);
size_t	ft_strlcpy(char *dst, const char *src, size_t dstsize);

#endif

get_next_line_bonus.c

#include "get_next_line_bonus.h"

char	*store_buf(char *buf, char *backup)
{
	char	*new_backup;
	int		new_len;

	if (!backup)
	{
		backup = (char *)malloc(sizeof(char));
		backup[0] = '\0';
	}
	if (!backup)
		return (NULL);
	new_len = ft_strlen(buf) + ft_strlen(backup);
	new_backup = (char *)malloc(sizeof(char) * (new_len + 1));
	if (!new_backup)
	{
		free(backup);
		return (NULL);
	}
	ft_strlcpy(new_backup, backup, new_len + 1);
	ft_strlcat(new_backup, buf, new_len + 1);
	free(backup);
	return (new_backup);
}

char	*read_and_store(int fd, char *backup)
{
	char	*buf;
	int		rbytes;

	buf = (char *)malloc(sizeof(char) * (BUFFER_SIZE + 1));
	if (!buf)
		return (NULL);
	rbytes = 1;
	while (rbytes != 0)
	{
		rbytes = read(fd, buf, BUFFER_SIZE);
		if (rbytes == -1)
		{
			free(buf);
			free(backup);
			return (NULL);
		}
		buf[rbytes] = '\0';
		backup = store_buf(buf, backup);
		if (ft_strchr(buf, '\n'))
			break ;
	}
	free(buf);
	return (backup);
}

char	*get_line(char *backup)
{
	char	*buf;
	int		i;

	i = 0;
	if (!backup[0])
		return (NULL);
	while (backup[i] && backup[i] != '\n')
		i++;
	buf = (char *)malloc(sizeof(char) * (i + 2));
	if (!buf)
		return (NULL);
	i = 0;
	while (backup[i] && backup[i] != '\n')
	{
		buf[i] = backup[i];
		i++;
	}
	if (backup[i] == '\n')
		buf[i++] = '\n';
	buf[i] = '\0';
	return (buf);
}

char	*backup_next(char *backup)
{
	char	*new_backup;
	int		i;
	int		j;

	i = 0;
	while (backup[i] && backup[i] != '\n')
		i++;
	if (backup[i] != '\n')
	{
		free(backup);
		return (NULL);
	}
	new_backup = (char *)malloc(sizeof(char) * (ft_strlen(backup) - i));
	if (!new_backup)
	{
		free(backup);
		return (NULL);
	}
	i++;
	j = 0;
	while (backup[i])
		new_backup[j++] = backup[i++];
	new_backup[j] = '\0';
	free(backup);
	return (new_backup);
}

char	*get_next_line(int fd)
{
	char		*buf;
	static char	*backup[256]; // fd최댓값 만큼의 문자열을 정적변수로 선언

	if (fd < 0 || fd + 1 > 256 || BUFFER_SIZE <= 0)
		return (NULL);
	backup[fd] = read_and_store(fd, backup[fd]);
	if (!backup[fd])
		return (NULL);
	buf = get_line(backup[fd]);
	backup[fd] = backup_next(backup[fd]);
	return (buf);
}

get_next_line_utils_bonus.c

#include "get_next_line_bonus.h"

char	*ft_strchr(const char *s, int c)
{
	size_t	i;
	size_t	len;

	i = 0;
	len = ft_strlen(s);
	while (i <= len)
	{
		if (s[i] == (unsigned char)c)
			return ((char *)(s + i));
		i++;
	}
	return (0);
}

size_t	ft_strlen(const char *s)
{
	size_t	i;

	i = 0;
	while (s[i])
		i++;
	return (i);
}

size_t	ft_strlcat(char *dst, const char *src, size_t dstsize)
{
	size_t	i;
	size_t	dst_len;
	size_t	src_len;

	dst_len = ft_strlen(dst);
	src_len = ft_strlen(src);
	i = 0;
	if (dstsize < dst_len + 1)
		return (dstsize + src_len);
	if (dstsize > dst_len + 1)
	{
		while (src[i] && dst_len + i + 1 < dstsize)
		{
			dst[dst_len + i] = src[i];
			i++;
		}
	}
	dst[dst_len + i] = '\0';
	return (dst_len + src_len);
}

size_t	ft_strlcpy(char *dst, const char *src, size_t dstsize)
{
	size_t	srcsize;
	size_t	i;

	srcsize = ft_strlen(src);
	i = 0;
	if (!dst || !src)
		return (0);
	if (dstsize == 0)
		return (srcsize);
	while (src[i] && i + 1 < dstsize)
	{
		dst[i] = src[i];
		i++;
	}
	dst[i] = '\0';
	return (srcsize);
}
profile
진짜 공부하자

0개의 댓글