프로젝트/42Seoul
42서울 본과정 - Get Next Line
soohykim
2025. 4. 11. 10:22
728x90
반응형
📒 Technical consideration
when
- 23.06.05 월 20:00 ~ 04:20
- 23.06.06 화 12:20 ~ 17:00
- 23.06.09 금 20:00 ~ 02:00
- 23.06.12 일 17:00 ~ 24:00 (평가)
내용
프로토타입 | char *get_next_line(int fd) |
제출 파일 | get_next_line.c, get_next_line_utils.c, get_next_line.h |
Makefile | NAME, all, clean, fclean, re |
외부 함수 | malloc, free, read |
매개변수 | 읽어들일 파일의 descriptor (서술자) |
📌 주의사항
- norm error 금지
- segmetation fault, bus error, double free 금지
- heap에 동적 할당된 메모리 해제 (메모리 누수 방지)
- Makefile
- -Wall -Wextra -Werror 플래그 지정
- relink 금지 (다시 make했을 때 재실행 금지)
- $(NAME), all, clean, fclean, re 규칙 포함
- 보너스 제출
- Makefile에 보너스 규칙 포함
- 프로젝트 메인 파트에서 금지되었던, 헤더/라이브러리/함수 추가
- _bonus.{c.h} 별도의 파일에 생성
- 사용 금지
- libft 라이브러리
- lseek() 함수
- 전역변수
📒 Mandatory part
📕 Get Next Line 정의
1) function prototype
char *get_next_line(inf fd);
2) header file
- 과제 구현
- <unistd.h> : 파일을 읽고 쓰고 닫기 (read, write, close 함수)
- <stdlib.h> : 힙 메모리에 동적할당 (malloc 함수)
- main으로 test
- <fcntl.h> : 파일 열기 및 파일 접근 모드 (open 함수, O_RDONLY)
3) description
- 개행으로 끝나는 한 줄을 읽고, 반환하는 함수 (호출 할 때마다 함수는 한 번에 한 줄씩)
- 읽는 파일에 NUL 문자('\0')는 없다고 간주
- 과제에 binary file일 때 undefined behavior인데, NULL 문자가 들어있는 파일은 binary file
- 컴파일러 호출시 : -D BUFFER_SIZE=n 옵션 추가
- read()의 buffer size를 지정합니다.
- 평가자와 Moulinette는 코드를 채점할 때 buffer size를 변경함
- -D BUFFER_SIZE플래그가 없어도 프로젝트가 컴파일이 되어야 함
4) return value
- 한 줄 제대로 읽을 경우 ➡ 읽은 라인의 문자열 반환
- \n이 존재할 경우 : 반환하는 문자열에 포함되어야 함
- 읽을 라인이 없거나 에러 발생할 경우 ➡NULL 반환
- \0 존재할 경우 ➡ 종료
5) process
- txt 파일을 read함수 이용하여 버퍼 사이즈만큼 읽고 반환
- read(fd, buf, BUFFER_SIZE);
- buf -> static_buf로 이동 후 문자열 개행이 있는지 확인
- 없을 경우 : buf 다시 한번 더 읽고 static_buf 이어 붙임
- 있을 경우 : 개행 전까지의 문자를 line에 할당 (개행 후 문자는 static_buf에 남겨둠)
📕 file descriptor
1) 개념
- 파일에서 개행을 만날 때 까지 한 줄을 읽어들여서 매개변수로 들어온 변수에 직접 할당
- 파일 디스크립터는 시스템으로 부터 할당받은 파일이나 소켓을 대표하는 정수 (파일 관리하기 위한 숫자)
2) fd value
- 0 : 표준입력 (Standard Input)
- 1 : 표준출력 (Standard Output)
- 2 : 표준에러 (Standard Error)
- 3 ~ : 사용되지 않은 숫자 중 가장 작은 숫자 부여
- -1 : 잘못된 파일에 접근할 경우, 에러 발생
📕 read 함수
1) 개념
#include <unistd.h>
size_t read(int fd, void *buf, size_t count);
fd : open 으로 열린 파일을 가리키는 파일 지정 번호
buf : 읽은 데이터를 저장할 공간
count : 읽을 데이터의 크기로 byte 단위
- bytes 수 만큼 fd를 읽어 buf에 저장
- 읽어온 바이트 수 반환
- 0 반환 : 0 (EOF) 파일을 끝까지 읽었으면, 다음번에는 더 이상 읽을 바이트가 없으므로 0을 반환
- -1 반환 : 실패시 -1을 반환
- fgets : 첫번째 행만 읽어서 반환
- read : 버퍼의 크기만큼 읽어서 반환
2) offset
- read(fd, buf, buffsize)하면 데이터를 읽은 만큼 offset를 증가
- 다시 파일을 읽으면 마지막 읽은 파일 위치부터 읽어들임
- 파일 디스크립터 테이블(FD Table)에서 읽은 위치(offset)를 저장함
- 프로세스가 같은 FD를 참조하여 같은 i-node를 참조하더라도, 각자 다른 부분을 읽을 수 있음
- 주기억장치와 주변 장치간 전송 속도 차이를 해결하기 위해 전송할 정보를 임시로 저장하는 고속 기억장치
- 파일을 읽을 때 사용할 임시 공간으로 버퍼 변수를 사용
- 특징 : 버퍼에는 용량이 한정되어 있기 때문에, 오버 플로우 현상이 발생할 수 있음
- 예시 : 표준 입출력함수 (scanf, printf 등)
📕 정적 변수
1) 개념
- 선언 : static int a;
- 초기화
- 초기화 하지 않는 경우 딱 한번만 0으로 초기화
- 상수로만 초기화 가능 (변수 불가능)
- 특징
- (지역변수처럼) 선언된 함수 내에서만 동작하지만, 함수가 끝나더라도 사라지지 않음
- (전역변수처럼) 프로그램이 시작될 때 메모리에 할당되고, 프로그램이 종료될 때 회수됨
2) static 쓰는 이유
- 동적할당은 GNL 함수가 끝나고 나면 저장한 문자열을 찾을 수 없음
- static 사용해야 다시 함수를 호출해도 같은 위치에 접근 가능
3) 지역변수, 전역변수, 정적변수
- 지역변수 (자동변수)
- 함수의 매개변수에서 사용되는 변수 (함수의 내부)
- 함수 안에서만 접근 가능하며, 함수를 벗어나면 사라짐
- 초기화하지 않으면 컴파일 에러가 나거나 쓰레기값이 저장됨
- 전역변수
- 어느 곳에서든 참조하여 사용 가능한 변수
- 모든 곳에서 접근이 가능하고, 프로그램 종료 전엔 메모리가 소멸되지 않음
- 전처리기 밑에 선언하며, 반드시 상수로 초기화 (초기값 정하지 않으면 0으로 자동 초기화)
- 정적변수
- 프로그램이 종료되기 전까지 메모리가 소멸되지 않는 변수
- 프로그램이 시작될 때 생성 및 초기화 되고 프로그램이 끝날 때 사라짐
- 초기화는 단 1번만 진행되고, 반드시 상수로 초기화 (초기값 정하지 않으면 0으로 자동 초기화)
- 스택이 아닌 데이터(Data) 영역에 저장됨 (비초기화시 BSS 영역에 저장)
📕 동적메모리 할당
1) 개념
void *malloc(size_t size)
함수 사용 : void *p = malloc(100);
- 동적 메모리 할당을 지원하는 C 표준 함수인 malloc을 사용해서 메모리를 할당
- size 변수에 지정한 크기만큼 힙(heap) 영역에 메모리를 할당 (Gbyte단위로 할당)
- 할당된 주소를 void* 형식으로 반환
2) free 함수
#include <malloc.h>
free(p); // p가 가지고 있는 주소에 할당된 메모리를 해제함
- 할당된 메모리 해제
- 힙에 할당한 메모리는 자동으로 해제되지 않음
- 할당되지 않은 메모리를 해제하는 경우
- 컴파일은 성공하지만, 실행할 때 오류 발생
- 할당된 메모리를 두 번 해제하는 경우
- 컴파일은 성공하지만, 실행할 때 오류 발생
📕 memory leak 확인 ( lldb debugging)
- 컴파일시 -g 옵션 : gcc -h *.c *.h
- 디버거 실행 : lldb a.out
- 프로그램 실행 : r or run
- breakpoint 걸기 : b 함수명/줄번호
- 다음 줄 실행 : n or next
- 함수 내부 실행 : s or step
- 변수 값 보기 : p 변수명 or print 변수명
- 종료 : exit
📘 구현
📌 get_next_line.c
- get_next_line
- ft_read_file (개행 찾을 때까지 파일 읽고, backup에 저장)
- ft_get_line (backup에서 출력부분 저장)
- ft_get_line (backup에서 출력 제외한 나머지 저장)
#include "get_next_line.h"
char *ft_get_line(char *bp)
{
int i;
int n;
char *line;
i = 0;
if (*bp == '\0')
return (NULL);
n = 0;
while (*(bp + n) != '\n' && *(bp + n) != '\0')
n++;
line = (char *)malloc(sizeof(char) * n + 2);
if (!line)
{
free(bp);
return (NULL);
}
while (*(bp + i) != '\0' && *(bp + i) != '\n')
{
*(line + i) = *(bp + i);
i++;
}
if (*(bp + i) == '\n')
*(line + i++) = '\n';
*(line + i) = '\0';
return (line);
}
char *ft_get_backup(char *bp)
{
int n;
int len;
char *new;
n = 0;
while (*(bp + n) != '\n' && *(bp + n) != '\0')
n++;
if (*(bp + n) == '\0')
{
free(bp);
return (NULL);
}
len = ft_strlen(bp);
new = (char *)malloc(sizeof(char) * (len - n + 1));
if (!new)
{
free(bp);
return (NULL);
}
len = 0;
while (*(bp + ++n) != '\0')
*(new + len++) = *(bp + n);
*(new + len) = '\0';
free(bp);
return (new);
}
char *ft_read_file(int fd, char *backup, char *buf, int n)
{
char *prev;
while (n != 0)
{
n = read(fd, buf, BUFFER_SIZE);
if (n == -1)
{
free(backup);
return (NULL);
}
buf[n] = '\0';
prev = backup;
if (!prev)
prev = ft_strdup("");
backup = ft_strjoin(prev, buf);
if (!backup)
{
free(prev);
return (NULL);
}
free(prev);
if (ft_strchr(backup, '\n') != NULL)
break ;
}
return (backup);
}
char *get_next_line(int fd)
{
static char *backup;
char *line;
char *buf;
size_t n;
if (fd < 0 || BUFFER_SIZE <= 0)
return (NULL);
buf = (char *)malloc(sizeof(char) * BUFFER_SIZE + 1);
if (!buf)
return (NULL);
n = 1;
backup = ft_read_file(fd, backup, buf, n);
free(buf);
if (!backup)
return (NULL);
line = ft_get_line(backup);
backup = ft_get_backup(backup);
return (line);
}
📌 get_next_line_utils.c
- libft 중 사용 함수
- ft_strlen (str 길이)
- ft_strjoin (backup과 읽은 buf 연결)
- ft_strchr (\n (개행문자) 검색)
- ft_strdup (malloc 후 strcpy)
#include "get_next_line.h"
size_t ft_strlen(const char *s)
{
size_t i;
i = 0;
if (!s)
return (0);
while (*(s + i) != '\0')
i++;
return (i);
}
char *ft_strjoin(char *s1, char *s2)
{
char *p;
size_t i;
size_t len;
if (!s1 && !s2)
return (NULL);
len = ft_strlen(s1) + ft_strlen(s2);
p = (char *)malloc(sizeof(char) * len + 1);
if (!p)
return (NULL);
i = 0;
len = 0;
while (*(s1 + i))
*(p + len++) = *(s1 + i++);
i = 0;
while (*(s2 + i))
*(p + len++) = *(s2 + i++);
*(p + len) = '\0';
return (p);
}
char *ft_strchr(char *s, int c)
{
size_t i;
i = 0;
while (*(s + i))
{
if (*(s + i) == c)
return (s);
i++;
}
return (NULL);
}
char *ft_strdup(const char *s)
{
char *p;
size_t i;
size_t len;
i = 0;
if (!s)
return (NULL);
len = ft_strlen(s);
p = (char *)malloc(sizeof(char) * (len + 1));
if (!p)
return (NULL);
while (*(s + i) != '\0')
{
*(p + i) = *(s + i);
i++;
}
*(p + i) = '\0';
return (p);
}
📌 get_next_line.h
- 연결리스트 이용시 : 구조체 생성
- get_next_line()의 프로토타입 존재 해야함
#ifndef GET_NEXT_LINE_H
# define GET_NEXT_LINE_H
# include <unistd.h>
# include <stdlib.h>
# ifndef BUFFER_SIZE
# define BUFFER_SIZE 42
# endif
size_t ft_strlen(const char *s);
char *ft_strjoin(char *s1, char *s2);
char *ft_strchr(char *s, int c);
char *ft_strdup(const char *s);
char *get_next_line(int fd);
char *ft_read_file(int fd, char *backup, char *buf, int n);
char *ft_get_line(char *bp);
char *ft_get_backup(char *bp);
#endif
📌 main.c
#include <fcntl.h>
#include <stdio.h>
int main(void)
{
int fd;
char *line;
fd = 0;
fd = open("./test.txt", O_RDONLY);
while ((line = get_next_line(fd)) != NULL)
{
printf("%s", line);
free(line);
}
if (line == NULL)
printf("%s\n", line);
close(fd);
return (0);
}
📒 Bonus part
📖 참고 📖 OPEN_MAX vs Linked List
728x90
반응형