get next line


1.get next line用途

* file descriptor을 통해 텍스트에서 eof까지 한 라인을 읽어 반환하는 함수. 
('\n'을 기준으로, '\n'이 나오기 전까지의 문자열을 line에 할당)

* 보너스 파트의 경우, `한 개의 static variable`만을 이용해 여러 쓰레드를 이용할 수 있게 한다. 
즉, 여러 개의 파일 디스크립터를 통해 여러 파일의 라인을 각각 읽을 수 있도록 한다.

2.get next lineプロトタイプ

int get_next_line(int fd, char **line);
int fd : 읽을 파일의 파일 디스크립터. fd를 통해 어떤 파일을 읽을 것인지 알 수 있다
char **line : 파일의 한 줄을 읽어 저장할 line변수.
이중 포인터로 받은 이유는 문자열의 주소값을 나타내기 때문이다.
반환값 : int형식의 반환값.

- 에러 발생 시 -1
- eof까지 읽었으면 0
- 한 줄을 반환했으면 1을 반환한다.

3.実施前に知るべき要因


3.1ファイルディスク立棒


  • file記述子はLinuxまたはUNIXシリーズシステムにおいてプロセスがファイルを処理する際に使用する概念であり,プロセスが特定のファイルにアクセスする際に使用する抽象値である.負数ではなく、整数値です.

  • パイプ、FIFO、ソケット、ターミナル、デバイス、または通常のファイルにかかわらず、開いているすべてのファイルを参照するために使用できます.

  • ファイルディスク立位0、1、2は、それぞれ標準入力、標準出力、標準エラーを表し、予め割り当てられている.よって、3から順に付与される.
  • 3.2 read関数


  • read関数の用途:open関数を使用して開いたファイルの内容を読み取る関数.fdは、n bytesを読み取り、bufに格納される.

  • タイトル:unistd.h

  • プロトタイプ:
  • ssize_t read (int fd, void *buf, size_t nbytes)
    int fd : 읽을 파일의 파일 디스크립터. fd를 통해 어떤 파일을 읽을 것인지 알 수 있다.
    void *buf : 읽어온 파일의 내용을 저장할 배열.
    size_t nbytes : 파일을 얼마나 읽을지 나타내는 변수.
    반환값 : read함수의 ssize_t 타입의 반환값. 읽어들인 데이터의 크기를 의미. 에러 발생 시 -1 반환.

    <注意>nバイトを返すとは限りません。


    eofまたは読み出しエラー時にnバイト未満の場合があります.
    ex)ファイル長が20バイト、nが100バイトの場合は20バイトしか読み込まないため、戻り値は100ではなく20です.
    読み取り中にエラーが発生した場合は、반환값은 -1です.

    3.3静的変数と地域変数の違い


  • 静的変数:静的変数はメモリのdata영역に格納されます.ゾーン変数とは異なり、関数呼び出しと終了時に値は初期化または削除されません.すなわち、関数ブロック、すなわち값이 제거되지 않는다から逸脱する.

  • ゾーン変数:ゾーン変数は스택 영역に格納されます.この領域は、関数内部で宣言された領域変数、戻り値などが格納され、関数呼び出し時に記録され、関数終了時に削除される.
  • 実装されるget next line関数は、「static変数」を使用して、以前に読んだ文字列を格納します。


    3.4ポインタ配列


    ポインタ配列:포인터배열.配列の要素として、ポインタ変数があります.
    <簡単な例>
    次のコードは、ポインタの配列の役割をテストするための簡単なコードです.
    #include <stdio.h>
    
    void test()
    {
        char    *array[3];
        
        array[0] = "hello~";
        array[1] = "this is a";
        array[2] = "test!";
        for(int i = 0; i < 3; i++)
        {
            printf("%s\n", array[i]);
        }
        return ;
    }
    
    int main(void)
    {
        test();
        return 0;
    }
    運転結果:
    ポインタ配列のインデックスはそれぞれ「hello~」、「this a」「test!」この3つの文字列の最初の文字のアドレス値が格納されます.

    4.実施上の注意事項


    (1)get next line()をメイン関数内部で繰り返し呼び出すことができる.


    すなわち、get next line()が呼び出されると、行にはnベースの行が格納され、2回目の呼び出しでも行には1つの行が格納されなければならない.

    (2)静的変数は、以前に読み取った値を記憶しなければならないため使用する。


    ex)BUFFER SIZEは10であり、ファイルには以下のテキストが格納されている。

    hellohi
    thisisa
    getnextline
    get next line()の初回呼び出し時のBUFFER SIZEは10で、hellohin(\n은 문자 하나で処理)が読み出されたnを含み、lineにhellohiが格納された後は1을 반환である.
    2回目にget next line()が呼び出されると、BUFFER SIZEは、nからisisangetnを含む10を読み出し、このaをlineに格納した後、1을 반환を読み出す.
    3回目にget next line()が呼び出された場合、extline(nを含む)に読み込まれ、lineにgetnextlineが格納された後、eofに読み込まれるため、0을 반환となる.
    重要な点は、既読データの変数を格納する必要があることです.
    ただし、この変数を領域変数として保存すると、関数の呼び出しと終了のたびにその変数が記録され削除されるため、以前は호출했었을 때의 값을 저장할 수 없다であった.

    したがって、この既読データを格納する変数は静的変数として格納される。


    (3)行を動的に割り当てる.


    nを基準としているため、行の長さは一定ではありません.動的に割り当てられます.

    (4)エラー処理部


    fdは음수가 될 수 없고であり、lineは文字列アドレスを格納するchar*変数である.したがって、ラインがNULLの場合、주소를 저장할 공간이 할당되지 않았다となる.
    なお、BUFFER SIZEが0以下であれば읽을 데이터가 0보다 작거나 같다となり、これは不可能である.この3つの条件に対してエラー処理を与える.

    (5)奨励の実施部分


    追加の部分は、複数のfdが1本の線を返すこともできるようにすることです.
    これを実現するために、これまでに読み取ったデータをバックアップする変数としてstatic 포인터 배열を使用します.
    このように、静的ポインタ配列として宣言されると、各fdはバックアップされた文字列をbackup[fd]で処理できるので、加算処理を行うことができる.
    バックアップされた文字列の最初のインデックスのアドレス値はbackup[fd]에 저장です.
    すなわち、backup[fd]は、backup여러 개의 문자열 처리であることができる文字列を意味する.

    (6)ft strjoin()を実施する場合は、カーネルガードを行う必要があります!!


    以前のlibftタスクを実行する場合、ft strjoin()を実装するときにNull Guardは必要ありません.したがって、ft strjoin()のパラメータs 1およびs 2のいずれかが空の場合、Segfaultが表示され、このget next lineタスクを行う場合、널가드를 꼭 진행である必要があります.
    ft strjoin()を最初に実行すると、get next line()が最初に呼び出されます.この場合、bufは既読文字列を含み、backup[fd]はNULLを含む.

    したがって、空きがないft strjoin()を実行するとsegfaultが表示されます。


    このような事態を防止するために、以前に実現されたlibftがアイドルにならなかった場合、꼭 널가드를 해 주는 것が推奨される.シールド付きft strjoin()は以下に実現する.

    (7)ft strdup()を使用してlineとbackup[fd]に文字列を入れる場合は、mallocが失敗したことに注意してください。


    私のコードではft strdup()を使用してlineとbackup[fd]に文字列を入れます.このとき注意すべき点はft_strdup()내부からmalloc에 실패である.
    私のコードではft strdupでmallocが失敗したときにnullが返され、get next line関数ではmallocが失敗したのは'에러'에 해당한다です.
    したがって、mallocへの割り当てに失敗したbackup[fd]をキャンセルし、-1を返す必要があります.これをコードとして実現したのは,下部のft error()関数である.

    (8)読み出し時に0を返しても(読み出していなくても)、backup[fd]に値が残っていることに注意する。


    backup[fd]に値が残っている場合は、読み出し時に値が読み込まれていなくても注意してください.
    backup[fd]に"n"がある場合は、"n"の前にlineに割り当て、"n"の後にbackup[fd]に入れます.
    backup[fd]に「n」がない場合、backup[fd]はlineに割り当てられ、backupは解放されます.

    5.実施形態

    (1) 보너스 파트 구현을 위해 static char *backup[OPEN_MAX]선언.
    
    OPEN_MAX는 단일 프로그램에 허용되는 `최대 열린 파일 수`를 정의하는 상수다. 
    Unix 시스템에서 C언어의 OPEN_MAX는 limits.h에 정의돼있다.
    그러나 허용되지 않은 헤더를 import하면 안되므로 나는 헤더에 OPEN_MAX를 정의해 놓았다.
    
    최대 파일 수만큼의 인덱스를 가지는 포인터 배열을 선언함으로써, OPEN_MAX만큼의 백업 문자열을 다룰 수 있게 되었다.
    
    (2) read를 통해 읽은 데이터를 저장할 char buf[BUFFER_SIZE+1]을 선언한 후 buf에 읽은 데이터 저장.
    
    크기가 'BUFFER_SIZE + 1'인 이유는 
    'ft_strjoin()'을 이용해 이전에 읽었던 데이터가 저장된 backup[fd] 문자열과 buf 문자열을 합하는데,
    문자열을 합치는 기준이 '\0'이기 때문에 buf의 맨 뒤에도 '\0'을 넣는다.
    
    (3) 읽은 데이터를 저장한 buf를 ft_strjoin()을 통해 backup[fd]와 합하고, 합한 문자열을 다시 backup[fd]에 넣는다.
    
    이 과정을 통해 지금까지 읽었던 문자열이 backup[fd]에 저장되어 있다.
    
    (4) '\n'을 찾고, 찾은 '\n'을 '\0'으로 바꿔 ft_strdup()을 이용해 line에 \n까지를 넣는다. 개행이 없으면 (2)로 돌아간다.
    
    (5) 아까 찾은 \n의 다음 index의 주소값을 ft_strdup()에 넣어, \n이후의 문자열을 static char *변수 backup[fd]에 넣는다.
    
    이 과정을 통해 \n이후의 문자열이 backup[fd]에 저장된다.

    すなわち,n,nより前の文字列は行に格納され,nより後の文字列はbackup[fd]に格納され,後で読み出すbufとマージされる.


    6.get next line utilsコード

    #include "get_next_line.h"
    
    size_t		ft_strlen(const char *str)//문자열의 길이를 반환
    {
    	size_t		index;
    
    	index = 0;
    	while (str[index] != '\0')
    	{
    		index++;
    	}
    	return (index);
    }
    
    char			*ft_strdup(const char *s1)//s1의 문자열을 복사한 새 문자열 반환
    {
    	char		*p;
    	size_t		slen;
    	size_t		index;
    
    	index = 0;
    	slen = ft_strlen(s1);
    	if (!(p = (char*)malloc(sizeof(char) * (slen + 1))))
    	{
    		return (0);
    	}
    	while (index < slen)
    	{
    		p[index] = s1[index];
    		index++;
    	}
    	p[index] = '\0';
    	return (p);
    }
    
    char	*ft_strjoin(char *s1, char *s2)//문자열 2개를 합하는 함수. get_next_line 과제 진행 시 NULL가드는 필수!!
    {
    	size_t	sindex1;
    	size_t	sindex2;
    	size_t	index;
    	size_t	strindex;
    	char	*str;
    	
    	if (!(s1) && !(s2))//문자열 둘 다 널이면
    		return NULL;
    	else if (!(s1) || !(s2))//문자열 둘 중 하나만 널이면 널이 아닌 문자열의 사본 반환
    		return (!(s1) ? (ft_strdup(s2)) : ft_strdup(s1));
    	sindex1 = ft_strlen(s1);
    	sindex2 = ft_strlen(s2);
    	index = 0;
    	strindex = 0;
    	if (!(str = (char*)malloc(sizeof(char) * (sindex1 + sindex2 + 1))))
    		return (NULL);
    	while (index < sindex1)
    		str[strindex++] = s1[index++];
    	index = 0;
    	while (index < sindex2)
    		str[strindex++] = s2[index++];
    	str[strindex] = '\0';
    	free(s1);//get_next_line진행 시 필요한 코드. s1과 s2 합했을 때 s1은 이제 필요가 없으므로 free시킴.
    	return (str);
    }

    7.get next lineコード

    #include "get_next_line.h"
    
    int		isin_newline(char *str)//str에서 '\n'이 있는 index를 찾는 함수. \n이 없으면 -1리턴
    {
    	int		index;
    
    	index = 0;
    	while (str[index] != '\0')
    	{
    		if (str[index] == '\n')//'\n'발견시 발견한 index 리턴
    		{
    			return (index);
    		}
    		index++;
    	}
    	return (-1);//'\n'발견하지 못했을 시 -1 리턴
    }
    
    int		ft_error(char **backup)//에러 발생 시 backup을 할당 해제하기 위한 함수 ft_error()
    {
    	while (*backup != 0)
    	{
    		free(*backup);
    		*backup = 0;//프리하기 전 0 넣으면 메모리 주소가 사라지게 되므로 free할 수 없음.
    	}
    	return (-1);
    }
    
    int					get_one_line(char **backup, char **line, int cut)//'\n'을 기준으로 '\n'전까지의 하나의 문자열을 line에 저장하기 위한 함수.
    {
    	char			*temp;
    
    	(*backup)[cut] = '\0';//'\n'을 '\0'으로 바꾼다.
    	if (!(*line = ft_strdup(*backup)))
    	{
    		return (ft_error(backup));//malloc에서 할당 실패했을 때 ft_error를 통해 backup을 할당 해제해 준다.
    	}
    	if (!(temp = ft_strdup(*backup + cut + 1)))
    	{
    		return (ft_error(backup));//malloc에서 할당 실패했을 때 ft_error를 통해 backup을 할당 해제해 준다.
    	}
    	free(*backup);
    	*backup = temp;
    	return (1);
    }
    
    int					get_last(char **backup, char **line)
    {
    	int				cut;
    
    	if (!(*backup))//file이 비어있을 때. backup[fd]가 아무것도 할당이 되지 않았으므로 여기에 들어간다.(ft_strjoin을 한번도 실행하지 않았을 때)
    	{
    		*line = ft_strdup("");
    		return (0);
    	}
    	else
    	{
    		if ((cut = isin_newline(*backup)) >= 0)//newline이 있으면
    		{
    			return (get_one_line(backup, line, cut));
    		}
    		//newline이 없으면
    		if (!(*line = ft_strdup(*backup)))//line에 *backup을 복사한 것을 넣되, malloc에 실패하면 ft_error 실행
    		{
    			return (ft_error(backup));
    		}
    		free(*backup);
    		*backup = 0;
    		return (0);
    	}
    }
    
    int					get_next_line(int fd, char **line)
    {
    	static char		*backup[OPEN_MAX];
    	char			buf[BUFFER_SIZE + 1];
    	int				readsize;
    	int				cut;
    
    	if ((fd < 0) || (line == 0) || (BUFFER_SIZE <= 0))//에러 처리
    		return (-1);
    	while ((readsize = read(fd, buf, BUFFER_SIZE)) > 0)//읽어들인 크기
    	{
    		buf[readsize] = '\0';//buf의 맨 뒤에 '\0'을 넣어 ft_strjoin()이 가능하게 함.
    		backup[fd] = ft_strjoin(backup[fd], buf);
    		if ((cut = isin_newline(backup[fd])) >= 0)//newline이 있으면 get_one_line()실행
    			return (get_one_line(&backup[fd], line, cut));
    	}
    	if (readsize < 0)//read시 에러났을 경우
    	{
    		return (ft_error(&backup[fd]));
    	}
    	return (get_last(&backup[fd], line));//읽어들인 게 0일 경우, backup에 남아있는 라인을 line에 저장.
    }

    8.実施方法


    (1)変数static char*backup[OPEN MAX]と変数buf[BUFER SIZE+1]を宣言し、以前に読み取ったデータを格納する.
    (2)BUFFER SIZEと同じfdをread()で読み出し、読み出したデータをbufに格納する.
    (3)readsizeで読み取ったデータの末尾に「0」を入れる.(ft strjoinを実行)
    (4)bufとbackup[fd]=ft strjoin(buf,backup[fd])でbufとbackup[fd]の文字列を加算した最終文字列をbackup[fd]に再配置する.
    (5)backup[fd]に「n」がある場合、get one line()で行に文字列を割り当て、「n」の後からbackup[fd]に再読み込みし、1を返します.
    (6)backup[fd]に"n"がない場合は(2)を返す.
    (7)ft strdup()の実行中にmallocが失敗した場合、ft errorによりbackup[fd]の割り当てがキャンセルされる.
    (8)EOFを読んだ場合はget last()を使用してbackup[fd]に"n"が存在するかどうかを比較し、"n"が存在する場合はget one line()を使用してlineに1行割り当て、0を返します.
    「n」がない場合は、backup[fd]を行にコピーし、backup[fd]を解放します.

    このポスターは李大賢のブログをたくさん参考にしています。


    ここにはきれいなコードと詳しい説明があるので、李代賢のブログを参考にすることをお勧めします!
    リンクテキスト