c fgets() 함수 구현하기

부제 : 42서울 get_next_line 구현하기


목적 : 파일 디스크립터로부터 한 줄을 읽고, 반환하는 함수 만들기

프로토 타입

조건 :

  • 변수는 정적 변수 하나만 사용 가능하다 (with a single static variable)
  • get_next_line() 함수를 이용해 여러 개의 파일 디스크립터를 관리할 수 있어야 한다.
  • malloc, free, read 3 개의 외부 함수만 사용 가능하다.
  • "get_next_line.c",
    "get_next_line_utils.c",
    "get_next_line.h"
    3개의 파일을 제출해야 한다.

알아야 할 것들은??

정적 변수 (static variable)

참고 :
https://en.wikipedia.org/wiki/Static_variable // 위키피디아
https://dojang.io/mod/page/view.php?id=690 // 코딩도장

함수 안에 있는 지역 변수라도, 변수가 사라지지 않게 한다.
함수를 벗어나도 변수가 사라지지 않고 유지되므로, 값을 변화시키거나 사용할 수 있게 된다.

간단한 예제 :

(코딩 도장 예제 참고)

read 함수

<unistd.h> 헤더 안에 들어 있다.

ssize_t (환경에서 가지고 있는 가장 큰 자료형, 64bit mac 에서는 long)
read (int fildes, void *buf, size_t nbyte)

파라미터 :
파일 디스크립터, 버퍼, 읽을 바이트의 개수

해당 파일 디스크립터에서 바이트의 개수만큼 파일을 읽고, 버퍼에 저장한다.

파일 디스크립터란?

파일 디스크립터(File Descriptor)란 리눅스 혹은 유닉스 계열의 시스템에서 프로세스(process)가 파일(file)을 다룰 때 사용하는 개념으로, 프로세스에서 특정 파일에 접근할 때 사용하는 추상적인 값이다.

유닉스 시스템에서는 모든 것이 파일이다.
일반적인 정규파일부터 디렉토리, 소켓, 파이프 등을 모두 파일로 관리한다.

프로세스가 실행 중에 파일을 Open하면 커널은 해당 프로세스의 파일 디스크립터 숫자 중 사용하지 않는 가장 작은 값을 할당해준다.

그 다음 프로세스가 열려있는 파일에 시스템 콜을 이용해서 접근할 때, 파일 디스크립터(FD)값을 이용해서 파일을 지칭할 수 있다.

사진 출처 : https://twofootdog.tistory.com/51

파일 디스크립터는 OPEN_MAX의 값만큼 할당이 가능하다.
( == 하나의 프로세스당 OPEN_MAX 개의 파일을 열 수 있다. )

이렇게 read, open, close 등의 함수에서 파라미터로 해당 파일 디스크립터를 이용하면 파일을 읽고 쓸 수 있는 것이다!

사진 출처 : https://z-man.tistory.com/151

기본 할당은 위와 같이 되어 있으며, 보통 해당 숫자가 아닌 다른 숫자들부터 할당하고 사용하기 시작한다.

버퍼란?

임시 저장 공간. 파일 디스크립터에서 nbyte 만큼 읽어낸 것을 buffer 에 저장한다.

gcc -d

gcc 의 전 처리 옵션중 하나.

#define [macroname]=[value] 을 넣어준다.


설계

이미 파일이 열려서, fd가 있다고 가정, BUFFER_SIZE가 주어지는 것을 유념하고 함수를 작성한다.

'\n' 을 만나면, 함수를 끝내고 return value 로 \n 이 포함된 문자열을 반환해야 한다.
읽어들일 문자열이 없거나, '\0'을 만나면 문자열을 반환하고 끝.

read를 사용할 때, buffer_size를 반드시 사용해야 한다.

buffersize 씩 읽어오는데, 중간에 '\n'이 있거나 '\0'이 중간에 있다면...?
읽어온 문자열을 검사해서,

'\n'이 있다면 문자열을 만들어준다.
'\0'이 있다면 문자열을 만들고, 종료하라는 signal 을 보낸다...?
#include "get_next_line.h"

#include <unistd.h>
#include <limits.h>
#include <stdio.h>
/* 임시___반드시 지울 것 */
#define BUFFER_SIZE 42

static void	make_line(char *new_str, char *buffer, char *buffer2, char *flag)
{
	size_t	idx;

	idx = 0;
	while (buffer[idx])
	{
		if (buffer[idx] == '\n')
			break ;
		idx++;
	}
	if (idx == BUFFER_SIZE)
		ft_strlcat(new_str, buffer, ft_strlen(new_str) + BUFFER_SIZE + 1);
	else
	{
		ft_strlcat(new_str, buffer, ft_strlen(new_str) + idx + 1);
		ft_strlcat(buffer2, buffer + (idx + 1), BUFFER_SIZE);
		*flag = 0;
	}
}

char	*get_next_line(int fd)
{
	char		*buffer;
	static char	buffer2[BUFFER_SIZE + 1];
	char		*line;
	char		flag;

	flag = 1;
	line = ft_calloc(1, 4096);
	buffer = ft_calloc(sizeof(char), (BUFFER_SIZE + 1));
	if ((fd < 0) || BUFFER_SIZE <= 0 || buffer == 0)
		return (0);
	if (buffer2[0] != '\0')
	{
		ft_strlcat(line, buffer2, ft_strlen(buffer2) + 1);
		ft_bzero(buffer2, (BUFFER_SIZE + 1));
	}
	while (flag)
	{
		if (read(fd, buffer, BUFFER_SIZE) == -1)
			return (0);
		make_line(line, buffer, buffer2, &flag);
		ft_bzero(buffer, BUFFER_SIZE + 1);
	}
	return (line);
}

처음에 작성한 파일이다.

buffer 안에 개행 문자가 여러 개 들어있을 경우를 커버하지 못했다.
추가 기능이 필요하다고 생각했다.

#include "get_next_line.h"

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

/* 임시___반드시 지울 것 */
#ifndef BUFFER_SIZE
# define BUFFER_SIZE 1000
#endif

// 메모리 관리 엄청 신경쓸 것!!!! 
char	*check_newline(char *buffer, char **tmp, char *line, char *flag)
{
	size_t	idx;

	idx = 0;
	while (buffer[idx])
	{
		if (buffer[idx] == '\n')
			break;
		idx++;
	}
	if (idx == BUFFER_SIZE)
	{
		line = ft_strjoin(line, buffer);
		ft_bzero(buffer, BUFFER_SIZE);
	}
	else
	{
		*tmp = buffer + (idx + 1);
		ft_strlcat(line, buffer, (ft_strlen(line) + idx + 2));
		*flag = 0;
	}
	return (line);
}

char	*check_tmp(char **tmp, char *line, char *flag)
{
	size_t	idx;

	idx = 0;
	while ((*tmp)[idx])
	{
		if ((*tmp)[idx] == '\n')
			break;
		idx++;
	}
	if ((*tmp)[idx] == '\0')
	{
		line = ft_strjoin(line, *tmp);
		*flag = 0;
	}
	else
	{
		ft_strlcat(line, *tmp, (ft_strlen(line) + idx + 2));
		*tmp = *tmp + (idx + 1);
		*flag = 2;
	}
	return (line);
}

char	*get_next_line(int fd)
{
	char		*buffer;
	static char	*tmp;
	char		*line;
	char		flag;

	flag = 1;
	if (tmp == 0)
		tmp = (char *)ft_calloc(1, sizeof(char));
	buffer = ft_calloc(BUFFER_SIZE, sizeof(char));
	line = ft_calloc(1, sizeof(char));
	if (buffer == 0 || line == 0 || fd < 0)
		return (0);
	while (flag) // if 문으로 변환, free 처음부터 꼭 넣어서 짜보자.
	{
		line = check_tmp(&tmp, line, &flag);
		if (flag == 2)
		{
			return (line);
		}
	}
	flag = 1;
	while (flag)
	{
		if (read(fd, buffer, BUFFER_SIZE) == -1) // 읽기 실패 했을때 모든 메모리 할당헤재
		{
			return (0);
		}
		line = check_newline(buffer, &tmp, line, &flag);
	}
	return (line); // 마지막 널을 반환하기 직전에 buffer랑 line을 할당해제해야함
}

2 번째 작성한 코드이다.

buffer 중간에 개행이 들어가 있는 것을 다 커버할 수 있게 코드를 작성했지만, 메모리 할당한 것들을 풀어주어야 했다.

  • 내가 사용하고 있는, 메모리의 주소를 옮겨 저장하는 방법으로는 free 할 수 없다.
    --> 인덱스를 사용해 접근하는 등의 방법이 필요하다.

기존에 사용하던 방법으로는 사용하기 힘들기 때문에, 다시 코드를 작성하고 util도 꼭 필요한 것들만 남겨두었다.


마지막으로 memory leaks, norminette 규칙을 다 맞춘 코드이다.

나는 끝까지 배열을 이용했다.

/* ************************************************************************** */
/*                                                                            */
/*                                                        :::      ::::::::   */
/*   get_next_line.c                                    :+:      :+:    :+:   */
/*                                                    +:+ +:+         +:+     */
/*   By: hyojeong <[email protected]>     +#+  +:+       +#+        */
/*                                                +#+#+#+#+#+   +#+           */
/*   Created: 2022/03/20 22:14:21 by hyojeong          #+#    #+#             */
/*   Updated: 2022/03/29 13:03:15 by hyojeong         ###   ########.fr       */
/*                                                                            */
/* ************************************************************************** */

#include "get_next_line.h"

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

static ssize_t	is_newline(char *str)
{
	ssize_t	idx;

	idx = 0;
	if (str == 0)
		return (-1);
	while (str[idx])
	{
		if (str[idx] == '\n')
			return (idx);
		idx++;
	}
	return (-1);
}

static char	*ft_read(int fd)
{
	char	*buffer;
	int		read_tmp;
	char	*tmp;

	buffer = ft_calloc(BUFFER_SIZE + 1, sizeof(char));
	tmp = ft_calloc(BUFFER_SIZE + 1, sizeof(char));
	read_tmp = 0;
	while (1)
	{
		read_tmp = read(fd, buffer, BUFFER_SIZE);
		if (read_tmp < 0)
		{
			free(buffer);
			free(tmp);
			return (0);
		}
		if (read_tmp == 0)
			break ;
		tmp = ft_strjoin(tmp, buffer, 2);
		if (is_newline(buffer) != -1)
			break ;
		ft_bzero(buffer, BUFFER_SIZE);
	}
	free(buffer);
	return (tmp);
}

static char	*split(char *tmp)
{
	ssize_t	idx;
	char	*buffer;

	if (tmp == 0)
		return (0);
	idx = is_newline(tmp);
	if (idx == -1)
		idx = ft_strlen(tmp);
	buffer = ft_calloc(idx + 2, sizeof(char));
	ft_strlcat(buffer, tmp, idx + 2);
	return (buffer);
}

static char	*remain_tmp(char *tmp)
{
	ssize_t	idx;
	char	*new_str;

	idx = is_newline(tmp);
	if (idx == -1)
	{
		free(tmp);
		return (0);
	}
	new_str = ft_calloc(ft_strlen(tmp) - idx, sizeof(char));
	ft_strlcat(new_str, tmp + idx + 1, ft_strlen(tmp) - idx);
	free(tmp);
	return (new_str);
}

char	*get_next_line(int fd)
{
	static char	*tmp;
	char		*buffer;

	if (fd < 0 || BUFFER_SIZE <= 0)
		return (0);
	if (is_newline(tmp) != -1)
	{
		buffer = split(tmp);
		tmp = remain_tmp(tmp);
		return (buffer);
	}
	tmp = ft_strjoin(tmp, ft_read(fd), 1);
	if (tmp == 0)
		return (0);
	if (tmp[0] == '\0')
	{
		free(tmp);
		tmp = 0;
		return (0);
	}
	buffer = split(tmp);
	tmp = remain_tmp(tmp);
	return (buffer);
}

먼저, get_next_line.h 헤더 include

read 함수가 들어있는 <unistd.h>
free 함수가 들어있는 <stdlib.h>

<참고>
OPEN_MAX가 들어있는 헤더는 <limits.h>

open으로 파일을 열지 못하면 fd는 -1을 반환하기 때문에 fd가 - 이거나, buffer size 에 대한 가드도 해주었다.

왜 버퍼 사이즈가 1, 9999, 10000000 이어도 작동하는가?

메모리의 구조에 달려있다. 메모리를 동적 할당하면, heap 영역에서 동작하는데 stack 영역의 메모리 제한 (윈도우 1mb, 리눅스 8mb)과 달리 heap 영역은 마음대로 할당할 수 있다.

만약, 할당 가능한 fd보다 더 큰 값이 들어온다면?? (보너스)

이런 상태는 오류라고 판단했다. 프로세스가 열 수 있는 파일의 수가 지정되어있으며, 그 이상 열려고 할 때는 -1을 반환한다. 그래서 그 수보다 더 큰 fd 가 들어온다면 터질 것이다. (오류 : null 혹은 터지는 것이 맞다고 생각한다.)

좋은 웹페이지 즐겨찾기