Home Get Next Line ④ 구현 과정
Post
Cancel

Get Next Line ④ 구현 과정

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문을 반복하는 동안 탐색했던 위치를 재탐색하는 비효율적인 과정을 거치게 된다.

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한다.

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에서 읽어들인 부분을 저장하기 위해 사용 (참고)

  • fd 유효성 체크 :
    • fd > OPEN_MAX : read 함수가 읽어들일 수 있는 최대 파일의 개수를 초과한 것
  • 배열의 크기 관련

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. 테스터 활용

gnlTester

  • 사용 방법
    • make m : mandatory 테스트
    • make b : bonus 테스트
    • make a : 둘 다 테스트

Ref.

static 변수
포인터 배열
프로젝트 참고

This post is licensed under CC BY 4.0 by the author.

Dangling pointer(댕글링 포인터)

Printf Manual