프로젝트 소개
get_next_line은 파일 디스크립터(fd)에서 한 줄씩 읽어오는 함수다. 이 함수는 여러 번 호출되어도 이전에 읽은 내용을 기억하고, 다음 줄을 반환한다. BUFFER_SIZE 매크로를 통해 읽기 버퍼 크기를 조절할 수 있다.
주요 특징
- 정적 변수를 사용하여 여러 파일 디스크립터 처리
- 버퍼 크기에 관계없이 한 줄씩 안전하게 읽기
- 메모리 누수 방지 및 안전한 메모리 관리
- 두 가지 구현 방식 제공 (문자열 기반, 링크드 리스트 기반)
구현 버전
이 프로젝트는 두 가지 버전의 구현을 제공한다:
Version 1 (기본 버전)
- 파일:
get_next_line.c,get_next_line_utils.c,get_next_line.h - 구조: 정적 문자열 캐시 사용
- 특징: 단순하고 직관적인 구현
Version 2 (링크드 리스트 버전)
- 파일:
get_next_line_v2.c,get_next_line_utils_v2.c,get_next_line_v2.h - 구조: 링크드 리스트를 사용한 캐시 관리
- 특징: 더 효율적인 메모리 관리
파일 구조
get_next_line/
├── get_next_line.h # Version 1 헤더 파일
├── get_next_line.c # Version 1 메인 함수
├── get_next_line_utils.c # Version 1 유틸리티 함수
├── get_next_line_v2.h # Version 2 헤더 파일
├── get_next_line_v2.c # Version 2 메인 함수
├── get_next_line_utils_v2.c # Version 2 유틸리티 함수
├── main.c # 테스트용 메인 함수
└── 42cursus_gnl_tests/ # 테스트 디렉토리
함수 설명
Version 1
get_next_line(int fd, char **line)
- 설명: 파일 디스크립터에서 한 줄을 읽어오는 함수
- 매개변수:
fd: 읽을 파일 디스크립터line: 읽은 줄을 저장할 포인터의 주소
- 반환값:
1: 성공적으로 한 줄을 읽은 경우0: 파일의 끝에 도달한 경우 (EOF)-1: 오류가 발생한 경우
유틸리티 함수
ft_strlen(const char *s): 문자열 길이를 반환하는 함수ft_strjoin(char *s1, char *s2, ssize_t r_size): 두 문자열을 결합하는 함수ft_substr(char const *s, unsigned int start, ssize_t len): 문자열의 일부를 추출하는 함수
Version 2
get_next_line(int fd, char **line)
- 설명: 링크드 리스트를 사용하여 파일 디스크립터에서 한 줄을 읽어오는 함수
- 매개변수: Version 1과 동일
- 반환값: Version 1과 동일
유틸리티 함수
ft_strdup2(char *str, ssize_t len): 문자열을 복사하는 함수lst_add(t_cache **cache, char *content): 링크드 리스트에 노드를 추가하는 함수hasnl(char *s): 문자열에 개행 문자가 있는지 확인하는 함수lst_hasnl(t_cache *cache): 링크드 리스트에 개행 문자가 있는지 확인하는 함수
컴파일 및 사용법
컴파일
Version 1을 사용하는 경우:
gcc -Wall -Wextra -Werror -D BUFFER_SIZE=42 get_next_line.c get_next_line_utils.c main.c -o gnl
Version 2를 사용하는 경우:
gcc -Wall -Wextra -Werror -D BUFFER_SIZE=42 get_next_line_v2.c get_next_line_utils_v2.c main.c -o gnl
사용 예시
#include "get_next_line.h"
#include <fcntl.h>
#include <stdio.h>
int main(void)
{
int fd;
char *line;
int ret;
fd = open("test.txt", O_RDONLY);
if (fd < 0)
return (1);
while ((ret = get_next_line(fd, &line)) > 0)
{
printf("%s\n", line);
free(line);
}
if (ret == 0)
printf("%s\n", line); // 마지막 줄 출력
free(line);
close(fd);
return (0);
}
표준 입력에서 읽기
./gnl
# 또는
echo -e "line1\nline2\nline3" | ./gnl
동작 원리
Version 1 (문자열 기반)
- 초기화: 정적 변수
cache를 빈 문자열로 초기화 - 읽기:
read()함수로BUFFER_SIZE만큼 읽어서 버퍼에 저장 - 결합: 읽은 내용을 기존
cache와 결합 - 개행 확인:
cache에 개행 문자(\n)가 있는지 확인 - 추출: 개행 문자가 있으면 그 전까지를
line에 저장하고, 나머지는cache에 유지 - 반복: 파일의 끝까지 반복
Version 2 (링크드 리스트 기반)
- 초기화: 정적 링크드 리스트
cache를 NULL로 초기화 - 읽기:
read()함수로 읽은 내용을 링크드 리스트 노드로 추가 - 개행 확인: 링크드 리스트를 순회하며 개행 문자 확인
- 추출: 개행 문자 전까지의 모든 노드 내용을
line에 결합 - 정리: 사용한 노드는 제거하고, 개행 문자 이후 내용은 첫 노드에 재배치
- 반복: 파일의 끝까지 반복
메모리 관리
- 각 버전은 메모리 누수를 방지하기 위해 사용한 메모리를 적절히 해제한다
line에 할당된 메모리는 호출자가free()로 해제해야 한다- 정적 변수는 프로그램 종료 시까지 유지되며, 여러 파일 디스크립터를 처리할 수 있다
주의사항
BUFFER_SIZE는 컴파일 시-D BUFFER_SIZE=n옵션으로 정의해야 한다line에 할당된 메모리는 사용 후 반드시free()로 해제해야 한다- 여러 파일 디스크립터를 동시에 처리할 수 있지만, 현재 구현은 단일 정적 변수를 사용한다
- 파일 디스크립터가 유효하지 않거나
line이 NULL인 경우 오류를 반환한다
테스트
프로젝트에는 42cursus_gnl_tests 디렉토리가 포함되어 있어 다양한 테스트 케이스를 실행할 수 있다.