본문 바로가기
42cursus/get-next-line

get_next_line(bonus)

by 인듯아닌듯 2020. 7. 11.

이중배열과 배열 포인터에 대한 이해

gnl의 bonus를 진행하기 위해서는 이중배열 char buffer[fd][BUFFER_SIZE + 1]을 memset할 수 있어야했다. -> 가능하다.!!

(이중배열을 써야하는 건가? (BUFFER_SIZE+1) * fd 이면 값이 꽤나 많이 클 것으로 예상됨)

char buffer_double[5][20];
    memset(buffer_double[1], 0, 20);
    printf("&buffer_double[2][0] p : %p\n", &buffer_double[2][0]);
    printf("&buffer_double[2]    p : %p\n", &buffer_double[2]);
    printf("buffer_double[2][0]  p : %p\n", buffer_double[2][0]);
    printf("buffer_double[2]     p : %p\n", buffer_double[2]);
    printf("buffer_double[2][0]  d : %d\n", buffer_double[2][0]);
    printf("buffer_double[2]     d : %d\n", buffer_double[2]);
    printf("*buffer_double[2]     d : %d\n", *buffer_double[2]);

&buffer_double[2][0] p : 0x7ffee3298738
&buffer_double[2]    p : 0x7ffee3298738
buffer_double[2][0]  p : 0x0
buffer_double[2]     p : 0x7ffee3298738
buffer_double[2][0]  d : 0
buffer_double[2]     d : -483817672
*buffer_double[2]     d : 0

[2]은 자동적으로 [2][0]의주소를 가리킨다.

배열 포인터는 배열의 주소가 아니라 배열의 첫번째 요소의 주소를 가리킨다. 왜냐하면 배열에는 주소가 없기 때문이다.

https://blockdmask.tistory.com/56

개행이후에 저장하는 문자열에 대한 생각.

BUFFER_SIZE가 엄청 큰 경우 저장하는 문자열에 '\n'이 많이 담길 수 있다. 이때는 read를 읽기전에 처리해주어야한다.

1. static char *remain_string[2055]로 개행 이후를 저장함.

2. linked_list로 구현


1번으로 잘 구현 한 것 같은데 자꾸 "할당되지않은 주소에 free를 하였습니다" 라는 에러가 발생한다.

/dev/null : 아무것도 씌여있지않은 unix의 특수 파일

open("/dev/null", O_RDONLY); // return 3;

line[0] == '\0';


1. fd가 음수, open으로 할당되지않은 fd값이 get_next_line(int fd, **line)에 들어갔을 때는 line에 빈문자열 할당해주고 -1 반환한다.

2. bufferr size가 음수이거나 0이면, line에 할당해주지않고 -1을 반환한다.

3. open에 의해서 이전에 생성되었던 fd값과 동일한 fd값이 생긴다면, static variable을 다시 사용하기때문에, 한번 끝까지 사용한 fd에 대해서는 static variable을 (char *)의 포인터를 NULL로 초기화 해줘야한다.

4. 존재하는 fd로 파일을 다 읽어서 read값이 0을 return한 이후에 다시 한번 get_next_line을 실행시켰을 때, 빈 문자열을 반환시켜야하는 테스터기가 있다. (https://github.com/Alexandre94H/gnl-war-machine-v2019)

//ft_strdup에 추가해준 사항
    if (str == NULL)
    {
        str_dup = malloc(sizeof(char));
        str_dup[0] = '\0';
        return (str_dup);
    }
//

#include "get_next_line.h"

int        get_next_line_sub(int fd, char **line)
{
    char        buffer[BUFFER_SIZE + 1];
    char        *oneline_string;
    static char    *remain_string[1000] = {0, };
    int            validation;

    oneline_string = NULL;
    if (exist_newline_in_remain_string(remain_string, fd, line) == 1)
        return (1);
    ft_memset(buffer, 0, (BUFFER_SIZE + 1));
    while ((validation = read(fd, buffer, BUFFER_SIZE)) > 0)
    {
        if (read_until_newline(buffer, fd, &oneline_string, remain_string) == 1)
        {
            *line = oneline_string;
            return (1);
        }
    }
    if (validation == -1)
    {
        *line = ft_strdup("");
        return (-1);
    }
    *line = ft_strdup(remain_string[fd]);
    free(remain_string[fd]);
    remain_string[fd] = NULL;
    return (0);
}

 


배운 점

1. 함수 밖을 빠져나가는 함수내에서 정의된 heap영역이 없게해야한다. 외부에서 실행한 free 함수가 함수 내부에 영향을 끼치면 안된다.

따라서 항상 함수 밖으로 내보내는 값은 ft_strdup을 이용해서 복사본을 내보내야 메모리누수를 미연에 방지할 수 있다.

(이런 이유때문에, malloc(0)이 1byte 크기의 동적할당해주는 이유 같음. malloc을 했다는 건 메모리공간이 발생했다는 것으로 규칙을 정하면, 쓰는 사람이 편할 수 있기때문에)

2. github에 있는 많은 테스트기를 이용하는 편이 좋다. 특히 내부구조가 간단할수록 에러가 났을 때 내가 고치기쉽고, 내용이 많은 경우에는 bash코드가 복잡하여 고치기는 오히려 어렵다.

3. 25줄 압박에서 벗어나고, segfault 및 memory leak을 피하기위해서는 항상 예외처리를 위한 함수를 만들어야한다.

ex) get_next_line(int fd, char **line)에서는 예외처리를 다루고, 실제로 다루는 함수는 get_next_line_sub(int fd, char **line)에서 다루도록한다.

4. 원하는 동작을 하는 프로그램을 만들 때, 디버깅을 하면서 접근해야한다. 또한 디버깅 역시 체계적으로 순서를 갖추어서 접근해야한다.(printf에서 적용) 그렇지 않으면 나중에 정말 힘들어진다 꼴도 보기싫어질 정도다.