42 June - 4. Get Next Line

수현·2023년 5월 23일
0

42Seoul

목록 보기
5/8

📒 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
MakefileNAME, 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 vs read 차이점

    • fgets : 첫번째 행만 읽어서 반환
    • read : 버퍼의 크기만큼 읽어서 반환

2) offset

  • read(fd, buf, buffsize)하면 데이터를 읽은 만큼 offset를 증가

    • 다시 파일을 읽으면 마지막 읽은 파일 위치부터 읽어들임
  • 파일 디스크립터 테이블(FD Table)에서 읽은 위치(offset)를 저장함

    • 프로세스가 같은 FD를 참조하여 같은 i-node를 참조하더라도, 각자 다른 부분을 읽을 수 있음

    📖 참고 📖 버퍼 (buffer)

    • 주기억장치와 주변 장치간 전송 속도 차이를 해결하기 위해 전송할 정보를 임시로 저장하는 고속 기억장치
      • 파일을 읽을 때 사용할 임시 공간으로 버퍼 변수를 사용
    • 특징 : 버퍼에는 용량이 한정되어 있기 때문에, 오버 플로우 현상이 발생할 수 있음
    • 예시 : 표준 입출력함수 (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)

  1. 컴파일시 -g 옵션 : gcc -h *.c *.h
  2. 디버거 실행 : lldb a.out
  3. 프로그램 실행 : r or run
  4. breakpoint 걸기 : b 함수명/줄번호
  5. 다음 줄 실행 : n or next
  6. 함수 내부 실행 : s or step
  7. 변수 값 보기 : p 변수명 or print 변수명
  8. 종료 : 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

profile
Notion으로 이동 (https://24tngus.notion.site/3a6883f0f47041fe8045ef330a147da3?v=973a0b5ec78a4462bac8010e3b4cd5c0&pvs=4)

0개의 댓글