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)값을 이용해서 파일을 지칭할 수 있다.
파일 디스크립터는 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 혹은 터지는 것이 맞다고 생각한다.)
Author And Source
이 문제에 관하여(c fgets() 함수 구현하기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@1984/c-fgets-함수-구현하기저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)