Mandatory part
과제 설명
char *get_next_line(int fd);
- 매개변수
- fd: 읽어야 할 파일의 디스크립터
- 반환값
- 읽은 line: 한 줄을 제대로 읽는다.
- NULL : 더이상 읽을게 없거나 에러 발생
- 사용가능 외부함수
- read, malloc, free
gnl
각각의 함수의 역할은 다음과 같다.
get_next_line : 3개의 함수를 호출
read_data : 데이터를 개행문자가 나올때까지 버퍼크기만큼 읽고 저장
extract_line : 맨 앞부터 개행문자까지 저장
save_rest : 개행문자 뒷부분 저장
get_next_line
전체적인 동작을 관리하는 함수
ex) text : abc\nde\nf\0
- 변수
char *line;
: 한 줄+개행문자를 저장해 리턴할 변수static char *backup;
: 다음에 읽을 데이터의 시작 주소1 2 3 4
char *text = "abc\nde\nf" char *line = "abc\n" char *backup = "de\nf" // 전부 맨 뒤에 '\0' 포함
- fd 유효성 체크
- 정적변수의 사용
static char *backup;
정적변수의 특성상 함수가 종료되어도 저장해놓은 문자열을 잃지 않는다.
지역변수로 선언되었다면 문자열을 잃어버려 메모리 누수가 발생한다.
read_data
데이터를 읽고 ‘\n’이 나올때까지의 line을 BUFFER_SIZE의 배수만큼 읽어들여 반환
ex) BUFFER_SIZE : 3, backup : abc\nde
- 변수
char *buffer;
: read해서 저장해 둘 데이터의 시작주소int bytes_read
: read한 데이터의 바이트수
- 동작방식
- read로 읽고 bytes_read에 읽은 바이트 저장
- bytes_read로
read
함수가 읽어들인 바이트 체크(0,-1,나머지) - strchr로 개행문자 체크
buffer[bytes_read]=0
: 버퍼의 데이터를 문자열로 만듦- 기존 backup에 읽어들인 buffer를 이어 붙인다.(ft_strjoin 기존 함수와의 차이점: NULL이 들어왔을 때 처리)
- buffer 메모리 해제 후 backup 반환
- 기타
- buffer 변수를 동적할당할 때 malloc 대신 calloc을 사용한 이유?
→ 맨 처음 while문이 동작할 때 buffer 안에는 쓰레기값이 들어있으므로 strchr이 ‘\n’을 검색하는 과정에서 의도치 않게 동작할 수 있다. - strchr이 backup이 아닌 buffer를 탐색하는 이유?
→ backup의 경우 행의 길이가 길어지면 while문을 반복하는 동안 탐색했던 위치를 재탐색하는 비효율적인 과정을 거치게 된다.
- buffer 변수를 동적할당할 때 malloc 대신 calloc을 사용한 이유?
extract_line
backup[fd]에서 \n을 포함한 앞부분 분리(리턴할 line)
ex) line : abc\n\0
- 변수
char *line
: 인수로 들어온 backup에서 개행문자 뒷부분을 제외한 데이터의 시작주소int i
: backup과 line 배열의 인덱스
- 동작방식
- null값 처리
- backup 배열의 값이 ‘\0’이거나 ‘\n’일 때까지 뒤로 이동해서 끝위치 파악
- 파악한 위치 활용해서 line에 메모리 할당(끝에 ‘\n’과 ‘\0’자리 포함)
- 다시 맨 앞부터 메모리 복사
- 맨 끝이 개행문자다? 개행저장하고 다음값을 null로
- 아니다? null만 복사
- 만든 line 반환
save_rest
backup[fd]에서 \n을 제외한 뒷부분 분리(저장할 부분)
ex) rest : de\0
- 변수
char *rest
: backup에서 개행문자 뒷부분을 저장할 포인터int idx1
: backup 배열의 인덱스int idx2
: rest 배열의 인덱스
- 동작방식
- backup의 맨 처음부분부터 돌면서 개행문자나 널문자가 나올때까지 이동
- 그대로 끝난다면 backup을 해제하고 널 반환
- rest에 메모리 할당(
ft_strlen(backup) - idx1 + 1
: 뒷부분길이 + 널값) - rest에 backup 뒷부분을 저장 후 backup은 메모리 해제
gnl_utils
- 사용한 함수
- ft_strlen
- ft_strlcpy-strjoin에서 사용
- ft_strchr
- ft_strjoin
- ft_calloc
- 기존 libft와의 차이점
- ft_calloc
count * size > SIZE_MAX
처리하는 부분 제외
- ft_strjoin
앞부분 인수로 들어온 s1이 NULL일 경우 크기가 1인 메모리공간을 할당한다. (맨 처음에 backup이 아무곳도 가리키지 않는 경우)
1 2 3 4 5
if (!s1) { s1 = (char *)malloc(1 * sizeof(char)); s1[0] = '\0'; }
끝부분에 s1을 free한다.
- ft_calloc
header
get_next_line.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef GET_NEXT_LINE_H
# define GET_NEXT_LINE_H
# include <stdlib.h>
# include <unistd.h>
# ifndef BUFFER_SIZE
# define BUFFER_SIZE 50
# endif
// gnl
char *get_next_line(int fd);
// gnl_utils
size_t ft_strlen(char *s);
size_t ft_strlcpy(char *dst, char *src, size_t dstsize);
char *ft_strjoin(char *s1, char *s2);
char *ft_strchr(char *s, int c);
void *ft_calloc(size_t count, size_t size);
#endif
표준 라이브러리
- stdlib.h
- malloc
- free
- unistd.h
- read
#define
- BUFFER_SIZE
- 컴파일시에 정의되는 경우 고려해서 ifndef 사용
Bonus part
파일 디스크립터 여러개를 관리할 수 있는 함수 만들기.
get_next_line
- 변수
static char *backup[OPEN_MAX + 1];
: 다음에 읽을 line의 시작 주소를 계속 저장해둘 변수
정적변수의 사용
static char *backup[OPEN_MAX];
***
과 같이 이중포인터를 사용한다면 array to pointer decay(배열의 크기를 잃어버리는 것)가 발생한다.- 포인터 배열
배열의 요소로 포인터를 갖는 배열
각각의 backup 배열의 요소에 각 fd에서 읽어들인 부분을 저장하기 위해 사용 (참고)
header
get_next_line_bonus.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef GET_NEXT_LINE_BONUS_H
# define GET_NEXT_LINE_BONUS_H
# include <stdlib.h>
# include <unistd.h>
# ifndef OPEN_MAX
# define OPEN_MAX 256
# endif
# ifndef BUFFER_SIZE
# define BUFFER_SIZE 50
# endif
// gnl
char *get_next_line(int fd);
// gnl_utils
size_t ft_strlen(char *s);
size_t ft_strlcpy(char *dst, char *src, size_t dstsize);
char *ft_strjoin(char *s1, char *s2);
char *ft_strchr(char *s, int c);
void *ft_calloc(size_t count, size_t size);
#endif
Test
1. 메인함수 활용
test_main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "get_next_line.h"
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
int main(void)
{
char *line;
int fd;
fd = 0;
fd = open("./test", O_RDONLY);
line = "\0";
while (line)
{
line = get_next_line(fd);
printf("%s\n", line);
free(line);
}
return (0);
}
테스트용 문서는 test라고 이름붙이고 같은 경로에 위치
방법 1. BUFFER_SIZE를 그대로 컴파일
gcc -Wall -Wextra -Werror get_next_line.c get_next_line_utils.c test_main.c
방법 2. BUFFER_SIZE를 변경하여 컴파일
gcc -Wall -Wextra -Werror -D BUFFER_SIZE=42 get_next_line.c get_next_line_utils.c test_main.c
BUFFER_SIZE 대신 OPEN_MAX를 넣어도 원하는 결과를 낼 수 있다. (표준 라이브러리(limits.h)를 include하지 않고 직접 상수를 define한 경우)
gcc -D
gcc -D [name]=[definition]
- #define을 프로그램 외부에서 정의하고 컴파일 시 반영할 수 있다.
gcc -Wall -Wextra -Werror -D BUFFER_SIZE=42 get_next_line.c get_next_line_utils.c
2. 테스터 활용
- 사용 방법
- make m : mandatory 테스트
- make b : bonus 테스트
- make a : 둘 다 테스트