[GET_NEXT_LINE] 구현 예시

IT/42Seoul|2021. 6. 11. 15:32

혹시나 문제가 된다면 바로 비공개 처리하겠습니다. 지적이나 댓글 환영합니다!

 

그저께 get_next_line의 구현을 끝내고 어제오늘 평가를 받아, gnl 과제를 보너스 점수 포함 115점으로 통과했다.

코드에 대한 이해가 낮아지기 전에 어서 코드리뷰를 하도록 해야겠다.

 

우선, get_next_line 과제에서는 3개의 파일을 구현하게끔한다.

get_next_line.c, get_next_line_utils.c, get_next_line.h 를 구현해야한다.

 

보너스 파트에서는 여러개의 fd를 사용하여도 각 fd의 내용에 대한 스레드가 유지될 수 있도록 해야하는데, 이 경우에는 static 변수를 2차원 배열로 만들면 해결된다. 따라서 보너스 파트와 기본 파트의 함수 구성을 똑같이 하였으므로 보너스에 대한 설명은 굳이 하지 않겠다.

 

주의해야할 점 !!!

1. 보너스 과제에 대해서는 보너스 헤더를 꼭 get_next_line_bonus.h로 선언하고, 보너스 과제에서 호출하는 헤더를 꼭 모두 이것으로 해주는 것을 주의해야한다. (보너스 과제인데 실수로 get_next_line.h 를 호출할 경우 망하는 거다...)

 

2. 문장은 개행을 중심으로 구분하여, 개행이 포함되지 않은, 개행의 앞부분을 뜻한다. 따라서 "\n" 1문장을 읽었을 때, line에는 "\0"이 들어가도록 해야한다.

 

3. 혼자서, 내 앞선 게시글에 나온 main함수로 디버깅 하는 경우에, printf("%d - %s\n", ret, line)을 실행하여, get_next_line()의 리턴값까지 같이 출력해주는 것이 정확히 디버깅 하는 꿀팁이니 참고하자!

 

 

 

참고로, 이번 과제에서는 메모리 누수에 대한 문제가 굉장히 중요하게 다루어진다. 따라서 메모리를 해제하고 널포인터로 만들어주는 과정을 꼼꼼히 구현하였다.

 

먼저 get_next_line_utils 의 함수들을 리뷰해보겠다.

get_next_line_utils.c 에서는 get_next_line.c에서 쓰이게될 함수들을 구현해놓았다.

 

 


 

(1) get_next_line_utils

 

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include "get_next_line.h"
 
size_t    ft_strlen(char *str)
{
    size_t    len;
 
    len = 0;
    while (str[len])
        len++;
    return (len);
}
 
int        find_newline(char *str)
{
    int    i;
 
    i = 0;
    while (str[i])
    {
        if (str[i] == '\n')
            return (i);
        i++;
    }
    return (-1);
}
 
char    *ft_strdup(char *s1)
{
    char    *ptr;
    size_t    len;
    size_t    i;
 
    i = 0;
    len = ft_strlen(s1);
    if (!s1)
        return (0);
    if (!(ptr = malloc(sizeof(char* (len + 1))))
        return (0);
    while (i < len)
    {
        ptr[i] = s1[i];
        i++;
    }
    ptr[i] = '\0';
    return (ptr);
}
 
char    *ft_strjoin(char *s1, char *s2)
{
    int        i;
    int        j;
    int        index;
    char    *str;
 
    i = 0;
    j = 0;
    index = 0;
    str = malloc(sizeof(char* (ft_strlen(s1) + ft_strlen(s2) + 1));
    if (!(str))
        return (0);
    while (s1[i])
        str[index++= s1[i++];
    while (s2[j])
        str[index++= s2[j++];
    str[index] = '\0';
    free(s1);
    s1 = 0;
    return (str);
}
 
cs

 

libft에서 구현한 함수들을 그대로, 혹은 수정하여 사용하였다.

 

- ft_strlen() : 문자열의 길이를 구하는 함수

- find_newline() : 주어진 문자열 내의 개행의 위치를 리턴하는 함수 (개행이 없다면 -1 리턴)

- ft_strdup() : 주어진 문자열을 복사하여 만든 새로운 문자열을 포인터를 리턴하는 함수 

- ft_strjoin() : 주어진 두 문자열을 합한 새로운 문자열의 포인터를 리턴하는 함수

 

 

여기에서 눈여겨보아야 할 부분은 추가로 ft_strjoin()에서 free(s1); s1 = 0; 처리를 해준 부분이다.

이 부분은 나중에 get_next_line.c에서 이어서 설명하겠다.

 

 

 

 


 

(2) get_next_line

 

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include "get_next_line.h"
 
int        error_return(char **backup)
{
    if (*backup)
    {
        free(*backup);
        *backup = 0;
    }
    return (-1);
}
 
int        get_one_line(char **backup, char **line, int nl_idx)
{
    char    *tmp;
 
    (*backup)[nl_idx] = '\0';
    if (!(*line = ft_strdup(*backup)))
        return (error_return(backup));
    if (!(tmp = ft_strdup(*backup + nl_idx + 1)))
        return (error_return(backup));
    free(*backup);
    *backup = tmp;
    tmp = 0;
    return (1);
}
 
int        get_all(char **backup, char **line)
{
    int        nl_idx;
 
    if (*backup && ((nl_idx = find_newline(*backup)) >= 0))
        return (get_one_line(backup, line, nl_idx));
    else if (*backup)
    {
        if (!(*line = ft_strdup(*backup)))
            return (error_return(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            read_size;
    int            nl_idx;
 
    if (fd < 0 || fd > OPEN_MAX || !line || BUFFER_SIZE <= 0)
        return (-1);
    if (!backup[fd])
    {
        if (!(backup[fd] = malloc(sizeof(char))))
            return (-1);
        backup[fd][0= '\0';
    }
    while ((read_size = read(fd, buf, BUFFER_SIZE)) > 0)
    {
        buf[read_size] = '\0';
        if (!(backup[fd] = ft_strjoin(backup[fd], buf)))
            return (-1);
        if ((nl_idx = find_newline(backup[fd])) >= 0)
            return (get_one_line(&backup[fd], line, nl_idx));
    }
    if (read_size < 0)
        return (error_return(&backup[fd]));
    return (get_all(&backup[fd], line));
}
cs

 

- static 변수는 여러개의 fd 를 처리할 수 있도록 2차원으로 선언하였으며, fd의 범위인 0 ~ OPEN_MAX 만큼 배열으로 만들어주었다.

read()에서 값을 받아올 버퍼 또한 buf라는 배열로 BUFFER_SIZE + 1 크기만큼 만들어주었다.

 

 

- get_next_line 은 3 개의 리턴값을 갖는다.

    - 오류 발생 시 : -1

    - EOF 도달 시 : 0

    - 1문장 읽음 : 1

 

 

 

 


1.  (- 1) 리턴하는 경우

- 먼저 프로그램이 시작될 때, fd가 범위에서 벗어나거나 line 의 주소가 존재하지 않거나, 버퍼사이즈가 0 이하인 경우에 대해 에러를 리턴하게끔 처리해주었다. (-1) 리턴

이 부분은 프로그램이 처음 시작될 때부터 검사되는 부분이다. 따라서 저 조건식에 걸린다면, get_next_line()을 처음 호출한 경우부터 해당 될 것이다. 그리고 그때는 backup에 메모리가 할당되지 않은 초기 상태일 것이다. 따라서 error_return()의 호출 대신, 단순히 (-1)만 리턴하는 것으로 처리한 것이다.

 

- malloc()이 사용되는 함수에서는 error_return ()을 호출하여 널 가드로 (-1) 을 리턴하게끔 처리하였다.

할당할 메모리가 부족하면 함수가 정상적으로 실행될 수 없으므로 오류가 발생한 상황이다. 따라서 -1 을 리턴하여 함수를 종료할 필요가 있다. 그러나 backup이라는 정적 변수에 할당된 메모리가 남아있으면 메모리 누수가 발생할 가능성이 있다. 따라서 (-1)을 리턴하기 전에 error_return()을 호출하였다. 이를 통해 backup에 남은 메모리가 있다면 이를 해제해준 후 널포인터로 만들어주는 작업을 하여, 메모리릭을 예방하였다.

 

- read()를 통해 읽어온 바이트가 음수일 경우는 오류이므로 (-1)을 리턴하게끔 처리하였다.

 

 


 

2.  0 또는 1 리턴하는 경우

- while()문의 조건식을 통해 read를 수행하고, 현재 읽어온 부분을 저번에 읽었던 부분에 합친다. (ft_strjoin() 사용)

read()를 통해 버퍼에 읽어온 문자열과 기존의 backup[fd]의 값을 합쳐 새로운 메모리를 할당한다. 이때 기존의 메모리 주소를 새롭게 합친 문자열의 메모리 주소로 업데이트하는 방식이기 때문에, 기존의 메모리를 free() 해주어야 메모리 누수를 예방할 수 있다. 이때문에 ft_strjoin() 내 에서, 첫번째 인자로 받은 문자열의 주소는 맨마지막에 free() 하도록 수정해 준 것이다.

 

그렇지만 맨 처음에 backup[fd] 에 메모리가 할당되어 있지 않다면, ft_strjoin()을 실행했을 때 backup[fd]를 free()할 수 없어 오류가 난다. 따라서, while()문 이전에 backup[fd]에 맨 먼저 임의로 1만큼 메모리를 할당하고 그 안에 값으로 '\0'을 넣어주었다.

(참고로, static 배열은 모든 요소가 처음에 0으로 초기화되어있다. 따라서 backup[fd]에 메모리 할당이 되지 않은 경우, backup[fd]의 초기값은 0이 된다는 것을 알아야한다)


- while() 실행 중에, 읽은 문자열 내에 개행이 존재한다면, get_one_line()을 호출하여 1문장만 추출하고 1을 리턴하도록 처리하였다.

 

- get_one_line 은 개행을 기준으로 개행 앞의 문장은 line에 붙여주고, 그 뒷부분은 static 변수에 복사하여 저장하는 역할을 한다.

이때 null을 만날 때까지 문자열을 복사하는 ft_strdup()를 사용하기 위해, 개행이 있던 부분의 인덱스 값에 먼저 '\0'을 할당해주었다. 

개행이 있던 부분의 뒷부분을 static 변수에 새로 복사하고 저장하는 과정에서, 기존의 메모리를 free 해주는 작업을 잊으면 안된다.


- while() 실행 중에, 읽은 문자열 내에 개행이 존재하지 않는다면 계속 반복해서 read()를 수행한다.

이 작업은 read_size 가 0이 될때까지 (EOF)에 도달할 때 혹은 중간에 개행이 나올 때까지 계속된다.

 

- 더이상 read()를 통해 읽을 값이 없다면 get_all()을 통해 남은 문자열을 처리해준다.

만약에 backup에 남은 문자열이 있다면, 그 안에 개행이 있는지 검사한다.

(버퍼사이즈가 클 경우, 개행이 있는 문장들을 이전에 한번에 여러개 다 읽어왔을 수가 있다)

만약 개행이 존재한다면 get_one_line()을 호출하여 그쪽으로 backup을 보내서 처리를 해준 후 1이 리턴되도록 한다.

(ex : 버퍼 사이즈가 커서 이전에 남은 문장들을 이미 다 읽어왔던 경우, backup에 남아있는 개행이 붙은 문장들이 1개 이상 존재할 수 있다. 예시를 들자면, 개행 8개로만 이루어진 텍스트파일이 있다고 가정하자)

 

그 안에 개행이 없다면, 아무리 길든 짧든, EOF로 끝나는 마지막 1문장이라는 뜻이다. 따라서 backup에 남은 문자열을 복사하여 line에 붙여준 후, backup의 메모리를 해제하고 이를 널포인터로 만들어준다. 함수가 -1 혹은 0으로 종료되기 전에 backup에 남은 메모리가 없어야 메모리 누수를 예방할 수 있다.

get_all 이 완료되었다면 EOF까지 도달했다는 뜻이므로 0을 리턴한다.

 

 

 

 

참고) 함수의 매개변수로 backup[fd]의 주소를 넘겨주는 이유는?

더보기

코드를 보면, get_one_line(&backup[fd], line, nl_idx) 이런 식으로 backup[fd]의 주소를 넘겨준다.

이렇게 해주는 이유가 무엇일까?

 

만약 backup[fd]를 그대로 넘겨주게 되면 backup[fd]의 주소가 가르키는, "해당 주소 내부"의 문자열 값에 접근할 수 있게 된다.

그러나 backup[fd]의 주솟값을 바꿀 수는 없다.

그렇지만 get_one_line() 함수는 backup[fd]의 기존 메모리를 free()하고 새로운 문자열을 담은 새로운 메모리를 backup[fd]에 할당해줘야한다. 즉 포인터가 가르키는 메모리의 주솟값이 바뀐다는 이야기다.

따라서 "해당 주소 내부의 값에 접근" 할 수 있게 하는 것과 동시에, "해당 주솟값을 변경"할 수 있게하기 위해서 backup[fd]의 주소를 넘겨준다고 보면된다.

 

참고) 버퍼사이즈로 큰 값(10000000)를 넣어줄 경우는 어떻게 될까?

더보기

segfault가 발생한다. buf[BUFFER_SIZE + 1]은 스택 메모리에 할당된 것이기 때문에 스택 사이즈를 초과하는 크기로 선언할 수 없다.

되도록이면 buf를 동적할당하고 싶었으나, 이럴 경우 리턴 전에 free하는 부분을 추가하느라 함수 구조가 더욱 복잡해지고, norminette 규정으로 정해진 함수의 줄 제한을 넘어가게 되었다. 따라서 어쩔 수 없이 정적인 배열로 선언하게 되었다. 

 

 

 

 


 

(3) get_next_line 헤더

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef GET_NEXT_LINE_H
# define GET_NEXT_LINE_H
 
# include <unistd.h>
# include <stdlib.h>
 
# define OPEN_MAX 1024
 
int        get_next_line(int fd, char **line);
size_t    ft_strlen(char *str);
int        find_newline(char *str);
char    *ft_strjoin(char *s1, char *s2);
char    *ft_strdup(char *s1);
 
#endif
 
cs

 

 

fd 의 개수 범위인 OPEN_MAX를 헤더파일을 통해 매크로로 정의해주었다.

OPEN_MAX를 통해 내가 최대한 열고자하는 파일의 개수를 정의해준 것이기 때문에 임의의 상수로 설정해주어도 상관이 없다.

이 외의 방법으로는 limits.h 헤더를 통해 open_max를 받아오는 방법도 있다. 그러나 이 경우는 허용되지 않은 외부 헤더를 호출했다며 디펜스에서 논란이 될 여지가 있다. 따라서 나는 상수로 정의해주는 것을 선택했다.

 

get_next_line_utils에서 size_t 자료형과 malloc()이 쓰이기 때문에 그에 필요한 헤더 2개 또한 include 해주었다.

그 외에 함수의 프로토타입 선언도 추가해주었다.

 

버퍼사이즈는 과제에 명시된 대로, 컴파일 단계에서 -D 옵션을 통해 정의해주는 것이다. 어차피 테스터나 뮬리넷에서는 컴파일 옵션으로 정의될 것이기 때문에 굳이 ifndef 으로 기본값을 정의해주지 않아도 될 것으로 생각했다. 따라서 BUFFER_SIZE는 매크로로 정의하지 않았다.

 

 

 

 

 

 


구현 과정에서 어려웠던 부분

 

1) 메모리 누수 부분을 잡는 것이 힘들었다.

특히 ft_strjoin()을 통해 첫번째 인자로 들어온 기존의 backup 메모리를 해제해주는 데, ft_strjoin()의 내부에서  backup의 주소가 null일 경우를 처리하는 것에서 자꾸 테스터기에서 오답이 났다. ft_strdup()을 사용하거나 여러 방법을 써봤는데도 테스트기를 통과하기 힘들었다.

 

 

2) 기존의 남아있는 개행을 가진 문장들이 한번에 backup에 전부 읽혔을 경우(버퍼사이즈가 큰 경우)를 생각하지 못했었다.

왜냐면 개행이 있으면 get_all() 실행이 되지 않고 매번 get_one_line()이 바로 호출되도록 이어진다고 오해했기 때문이다.

그러나 버퍼사이즈가 크면 read() 실행 시 EOF까지 모두 읽어올 수도 있으며, 처리하고 남은 문장들은 backup[fd]에 남는다. 따라서 다음번 호출부터는 새로 read()를 실행하지 않기 때문에, backup 변수에 남아있는 개행이 있는 문장까지 get_all()에서 처리해줘야한다.

따라서 get_all() 함수에서 이를 처리하는 조건문을 추가하는 데 시간이 걸렸다.

 

 

 

 

어떻게 해결했나?

 

1) 그냥 ft_strjoin()에 들어가기 전에 backup[fd]에 할당된 메모리가 없는지 체크했다. 메모리 주소가 null 이라면 시작부터 backup[fd]에 메모리를 1칸 할당해주었다. 이 경우에도 오류를 피하지 못했으나, 할당된 메모리의 값으로 '\0' 을 넣어주고나니 그제서야 테스터를 통과할 수 있었다.

 

 

2) backup에 개행을 가진 문장들이 남아있을 경우를 고려하여, get_all()에서도 backup의 개행여부를 체크하여 get_one_line()을 호출하는 코드를 추가하였다.

 

 

 

 

 


 

동료평가 하면서 새로 알게 된 지식 

 

1) 정적인 배열은 스택 메모리 영역을 사용하고, 동적인 메모리 할당은 힙 메모리 영역을 사용한다. 그리고 static 자료형은 데이터 영역을 사용한다.

static 자료형은 스택이나 힙이 아닌, 데이터 영역을 사용하여 메모리를 저장하는 것이기 때문에 메모리의 값을 계속 유지할 수가 있는 것이다.

이때, 주의해야할 점을 하나 알 수 있다.

일반 포인터에, static 메모리의 주소를 붙여주면 오류가 날 수 있다.

데이터 영역의 메모리에 static이 아닌 포인터가 접근하는 것이기 때문이다.

따라서 strdup을 통해 힙메모리로 static 메모리의 내용을 복사해준 후 포인터에 해당 힙메모리의 주소를 붙여주는 것이 바람직하다.

 

 

2) 주로 데이터를 읽어오는 단위를 2의 n 승으로 선언하게 된 관례 :

주로 메모리의 크기가 2의 배수로 선언되어 있기 때문에, 메모리를 한번에 가져오는 단위를 2의 n승으로 하는 것이 더 효율적으로 작용한다.

 

 

3) read()가 음수를 반환하는 경우? : 에러 발생

  - EAGAIN : O_NONBLOCK으로 열렸지만 즉시 읽을 수 있는 데이터가 없다

  - EIO : I/O 에러 발생

  - EFAULT : buf가 접근할 수 없는 주소 공간을 가리킨다

 

 

 

 


 

추가) malloc() 실패 시 널가드 처리를 통해 get_next_line() 함수에서 (-1)이 반환될 수 있도록 하는 처리를 해주었다.

그런데 이때 malloc()이 쓰이는 함수인 ft_strdup()에 대해 get_all() 내에서 이 처리를 딱 하나 빼먹은 것을 이후에 발견하였다.

따라서 게시글의 코드를 다시 수정하였다. 이 부분에서 실수를 하지않도록 꼼꼼히 점검해야한다. 내 친구도 이 부분을 딱 하나 빠뜨려서 디팬스에 실패하였다고 한다.

 

 

 

 

참고 자료 링크 : 

댓글()

[GET_NEXT_LINE] 구현 준비

IT/42Seoul|2021. 6. 4. 20:12

혹시나 문제가 된다면 바로 비공개 처리하겠습니다. 지적이나 댓글 환영합니다!

과제 중에 동료평가용 설명 정리 차원으로 블로그 글을 작성하기 때문에, 통과 완료 전까지는 계속 글을 수정할 수도 있습니다. 양해바랍니다. 감사합니다.

 

참고로, 이 포스팅의 중요한 본론은 (5), (6), (7) 부터 나옵니다.

 

libft가 조금 늦게 끝난 감이 있지만, get_next_line을 지금이라도 구현하게 되어서 다행이라고 생각한다. 처음에 get_next_line을 보고 좀 난감하다는 생각을 했다.

예를 들자면, "1줄씩 자른다" 라는 것은 문자열 포인터로 전체 문단이 주어진 경우에는 쉽다. 문자 "\n"를 기준으로, while문을 처음부터 문자열 끝까지 돌며 strdup() 함수 등을 사용하며 문자열을 분리해주면 된다.

 

그러나, 이번 문제는 주어진 BUFFER_SIZE만큼 fd를 이용해, 문자열(char *)이 아닌 "파일"을 읽어온다.

그러면 이때 어떤 처리를 해야, BUFFER_SIZE가 어떻게 주어지던 간에, 파일을 1줄씩 읽어오는 함수를 구현할 수 있는지 생각해보았다. 

 

 


 

(1) 과제의 목표 / 구현해야할 사항

 

* 이번 과제에서는 C에서 사용되는 "정적 변수"란 무엇인지를 배울 수 있다.

 

* 과제에 명시된 고려사항 (번역)

 

더보기
  •  get_next_line()을 반복문을 통해 실행할 때, 한번 루프가 돌때마다, fd의 텍스트를 1문장씩 읽는다(EOF 전까지)
  • 파일이나 표준입력으로부터 읽어왔을 때 함수가 잘 동작되도록 해야한다
  • libft 는 이 프로젝트에 허용되지 않는다. get_next_line_utils.c에 get_next_line에 필요한 함수들을 포함시킬 수 있다.
  • 프로그램을 -D BUFFER_SIZE=xx 이라는 컴파일 옵션과 함께 컴파일한다. 이것은 get_next_line에서 실행되는 read()의 버퍼 사이즈로 사용될 것이다.
  • 컴파일은 이런 형식으로 이루어진다 : gcc -Wall -Wextra -Werror -D BUFFER_SIZE=32 get_next_line.c get_next_line_utils.c
  • read()는 파일이나 표준입력으로부터 읽어올 때 컴파일할 때 정의된 BUFFER_SIZE를 사용해야한다.
  • get_next_line.h 헤더파일은 최소한 함수 get_next_line의 프로토타입을 포함해야한다.
  • 당신의 함수가 BUFFER_SIZE 값이 9999일 때 여전히 작동하는 가? BUFFER_SIZE 값이 1일 때는? 또 10000000일 때는? 그렇게 되는 이유를 아는가?
  • get_next_line 호출 시마다 최대한 적게 읽을 수 있도록 노력해야한다. 만약 당신이 개행을 만나면, 현재 문장을 리턴해야한다. 파일 전체를 읽고 각 문장을 처리하는 것은 하는 것은 금지된다.
  • 당신의 프로젝트를 테스트 없이 제출하지 말도록 한다.  당신의 모든 경우에 대비하여 실행할만한 많은 테스트들이 있다. 파일, redirection 그리고 표준입력으로부터 읽는 것을 시도해보라. 당신의 프로그램이 당신이 표준출력으로 개행을 보냈을 때는 어떻게 동작하나? 그리고 CTRL-D 를 할 경우는 또 어떤 동작을 보이는가?
  • 우리는 get_next_line이 정의되지 않은 동작을 가진다고 가정한다. 만약, 2번의 호출 사이에, 첫번째 fd가 EOF에 도달하기 전에 같은 파일 디스크림터가 다른 파일로 바뀌는 경우에 말이다.
  • lseek()는 허용되지 않는 함수이다. 파일을 읽어오는 것은 한번으로 끝나야한다.
  • 마침내 우리는 get_next_line은 binary 파일을 읽어오는 중에 정의되지 않은 동작을 가진다고 가정한다. 그렇지만, 당신이 바란다면, 당신은 이러한 동작을 일관적으로 만들 수 있다.
  • 전역 변수의 사용은 금지된다.
  • 정적 변수란 무엇인지 알아보자 : https://en.wikipedia.org/wiki/Static_variable

 

 

* 보너스 구현에 관한 고려사항 (번역)

 

더보기
  • 기본 파트가 완벽하지 않으면 보너스는 채점되지 않는다.
  • 보너스 파트를 위해서는 3개의 초기 파일에 모두 _bonus를 붙여야한다
  • get_next_line을 1개의 static 변수를 사용하여 성공한다.
  • get_next_line에 여러 개의 fd가 사용될 수 있도록 한다. 예를 들자면, fd 3, 4, 5가 읽을 수 있는 파일 디스크립터라고 하자, 이때 get_next_line을 fd를 3으로 하여 호출하고, 또 한번은 fd를 4로 하여 호출하고, 또 한번은 fd 를 5로 하여 호출하거나 할 수 있다. 이때 각각의 fd에서 읽던 스레드를 잃지 않아야한다.  

 

 

 

 

 


 

(2) static 변수(정적 변수)란 무엇인가?

 

- 정적으로 할당되는 변수

프로그램이 종료 될 때까지 메모리 유지 : 프로그램이 종료되지 않으면 함수가 종료되어도 값이 유지됨

- 초깃값을 지정하지 않아도 자동으로 0으로 초기화된다 : 초기화는 처음 한번만 수행하고 이후는 무시

- 자료형 앞에 static 키워드를 붙여 선언한다.

 

 

 

참고) 정적 전역 변수는 자신이 선언된 소스 파일 안에서만 사용할 수 있고, 외부에서는 가져다 쓸 수 없다.

즉, 전역 변수에 static을 붙이면 변수의 범위를 파일 범위로 제한하는 효과를 낸다.

 

참고) 정적 메모리 할당은 일반적으로 관련 프로그램을 실행하기에 앞서 컴파일 시간에 메모리를 할당한다.

 

참고) 정적 변수는 함수의 매개변수로 사용할 수 없다. 매개변수에 static을 붙이더라도 매개변수는 정적 변수가 되지 않으며 값이 유지되지 않는다.

 

 

 

 


 

(3) 파일 디스크립터 (fd)란 무엇인가?

 

1) 시스템으로부터 할당 받은 파일을 대표하는 0이 아닌 정수 값

2) 프로세스에서 특정 파일에 접근할 때 사용하는 추상적인 값

3) 프로세스에서 열린 파일의 목록을 관리하는 테이블의 인덱스

 

- 유닉스 시스템에서 모든 것은 "파일"이다. 모든 객체들은 파일로써 관리된다.

- 유닉스 시스템에서 프로세스가 이 파일들을 접근할 때에 파일 디스크립터라는 개념을 이용한다.

 

- 파일 디스크립터는 '0이 아닌 정수' 값이다. 즉, 음수가 아닌 0과 양수인 정수 값을 갖는다.

프로세스가 실행 중에 파일을 Open 하면 커널은 해당 프로세스의 파일 디스크립터 숫자 중에 사용하지 않는 가장 작은 값을 할당해 준다.

그 다음 프로세스가 열려있는 파일에 시스템 콜을 이용해서 접근할 때, FD 값을 이용해 값을 지칭할 수 있다.

 

프로그램이 프로세스로 메모리에서 실행을 시작할 때, 기본적으로 할당되는 파일 디스크립터들이 있다. 바로 표준 입력, 표준 출력, 표준 에러이다. 이들에게 각각 0, 1, 2 라는 정수가 할당된다.

0이 아닌 정수로 표현된느 파일 디스크립터는 0 ~ OPEN_MAX까지의 값을 가질 수 있으며, OPEN_MAX 값은 플랫폼에 따라 다르다.

 

이미지 출처 :  http://itnovice1.blogspot.com/2019/08/linux-file-descriptor.html

 

파일 디스크립터는 위에서 볼 수 있듯이, 단순히 숫자인 이유는 프로세스가 유지하고 있는 FD 테이블의 인덱스이기 때문이다. FD 3번이라는 의미는 FD 테이블의 3번 항목이 가리키는 파일이라는 의미이다.

프로세스는 이런 FD 테이블과 파일 테이블의 정보를 직접 고칠 수 없으며, 반드시 커널을 통해서 수정해야 한다.

 

 

 

 


 

(4) open, read 함수

 

* open() : 파일을 여는 함수

 

헤더 : #include <fcntl.h>

형태 : int open(const char *filepath, int flag);

            int open(const char *filepath, int flag, mode_t mode);

인수 :

         - char *FILENAME : 열고자하는 파일의 경로

         - int flags : 파일 열 때 사용할 옵션 (자세한 옵션은 더보기에)

더보기

O_RDONLY : 읽기 모드 (Read Only)

O_WRONLY : 쓰기 모드 (Write Only) - 읽지 않고 쓰기만 하는 경우는 크게 많지 않음

O_RDWR : 읽기/쓰기 모드

O_CREAT : 파일 생성

O_APPEND : 파일을 쓰되 기존 파일의 맨 끝부터 이어 쓰는 기능

O_TRUNC : 파일을 초기화

O_EXCL : O_CREAT 와 함께 사용되며, 이미 파일이 존재한다면 에러를 리턴

      - mode_t mode : O_CREAT 옵션을 쓸 때 필수적으로 사용해야하는 옵션으로, 파일의 접근 권한을 명시

         기본 값 ( 파일 : 0666 / 디렉토리 : 0777)

 

반환 : 성공적으로 수행 시, 음이 아닌 정수형의 값이 반환(= file descripter), 실패하면 -1 반환

 

 

- open()과 fd

 정상적으로 open한 파일의 위치를 가리키는 번호가 저장된다. 0, 1, 2 는 시스템적으로 건들면 안되는 플래그이므로 3부터 번호를 부여한다. 만약 하나의 C 파일에서 2개의 파일을 open했고, 두 파일 모두 정상적으로 open되었다면 처음 open한 파일의 fd는 3, 두 번째 open한 파일의 fd는 4다. 이렇게 순서대로 번호가 부여된다. 추가적으로 동일한 파일을 두번 open 해서 서로 다른 fd1과 fd2에 할당한다면, 그것 또한 3과 4로 별개의 번호가 부여된다.

 

 

 

 

* read() : 파일을 읽어오는 함수

 

헤더 : #include <unistd.h>

형태 : ssize_t read(int fd, void *buf, size_t nbytes);

인수 : int fd : 파일 디스크립터

         void *buf : 파일을 읽어들일 버퍼

         size_t nbytes : 버퍼의 크기

반환 : 실패 시 -1, 정상적으로 실행되었다면 읽어들인 바이트 수

 

 

 

 

참고) close 함수 :  open된 해당 fd 를 가진 file을 close 함

더보기

헤더 : #include <unistd.h>

형태 : int close(int fd)

인수 : int fd : 파일 디스크립터

반환값 : 정상적으로 close() 했다면 0을, 실패했다면 -1을 반환

 

참고) lseek 함수 : 읽기 및 쓰기를 위해서 파일의 위치를 재지정

더보기

헤더 : #include <sys/types.h>

         #include <unistd.h>

형태 : off_t lseek(int fildes, off_t offset, int whence);

설명 : lseek() 는 열린 파일 지정자 fildes로부터 offset만큼 위치를 변경한다. 위치 변경 시 기준점을 정할 수 있는 데 whence를 이용해서 지정할 수 있다. 실수로 파일의 마지막을 초과해서 lseek을 사용했을 경우, lseek에서 리턴을 하지는 않지만, write() 혹은 read()에서 에러를 발생하게 되므로 주의해야 한다.

  • SEEK_SET : 파일의 처음을 기준으로 offset 계산
  • SEEK_CUR : 파일의 현재 위치를 기준으로 offset을 계산
  • SEEK_END : 파일의 마지막을 기준으로 offset을 계산

반환값 : 성공했을 경우 파일의 시작으로부터 떨어진 byte만큼의 offset을 리턴. 실패했을 경우 -1을 리턴.

에러 : 

  • EBADF : Fildes가 열린 파일 지정자가 아니다
  • ESPIPE : Fildes가 파이프, 소켓 혹은 FIFO이다
  • EINVAL : Whence가 유효한 값이 아니다

 

 

 

 

 


 

(5) read 함수로만 파일을 읽고, 그대로 출력할 경우 어떻게 되는지

 -> 여기서 한문장씩 잘라서 읽게 하려면 어떤 고려사항을 생각해봐야하는 지

 

read()만 사용하여 파일을 읽고 읽은 부분을 바로 출력할 경우 어떤 일이 일어나는 지, 직접 open()과 read()를 실행해서 알아보았다.

 

main 함수와 컴파일 명령어

위 코드와 컴파일 명령어로 메인함수를 돌려서, 어떤 일이 일어나는 지 알아보았다. (BUFFER_SIZE를 컴파일 옵션을 통해 정의해줌)

(우선, open() 함수를 위해 <fcntl.h> 를, read() 함수를 위해 <unistd.h> 를 include 하였다)

맨 먼저 open()을 통해 파일의 fd 를 받아, 그 fd를 인자로 read()를 수행해서 버퍼 line에 읽어온 부분을 저장하였다.

 

그리고 읽어온 부분을 printf로 출력하기 위해서, 읽어온 바이트의 끝에 "\0"을 붙여 문자열처럼 null로 끝나게 해주었다.

그다음 읽어온 부분을 printf()로 출력하여 읽어온 부분의 결과를 확인하였다.

 

EOF까지 읽은 후에 read()를 다시 실행하면, 아무것도 버퍼에 읽어오지 않으며 리턴값으로 받은 read_size(read()를 통해 읽어온 바이트 수)는 0이 된다. 그 결과로 위의 main 함수에서는 while 문의 조건을 벗어나 while문을 빠져나오고 main 함수를 종료하게 된다.

 

 

주의 : read()를 통해 버퍼에 읽어온 파일에 다시 read()를 사용하면, 전에 읽은 부분의 뒤부터 읽기 시작한다는 점을 기억하자

주의 : 만일 open()을 다시 실행하고 받아온 fd로 read()을 실행할 경우, 처음부터 파일을 읽는다.

 

 

참고로 test.txt에는 팝송 lemon tree 의 가사 일부가 있다.

lemon tree 의 가사 일부

 

main 함수를 실행한 결과

 

위의 결과를 통해 경우를 나눠 분석해 볼 수 있다.

 

 

1) BUFFER_SIZE 가 1문장의 길이보다 긴 경우

 먼저 0번째 출력을 보자, 첫번째 문장은 "~ i wonder why"에서 끝나야 하는데, (\n)yester 까지 출력이 되버렸다. 즉, 뒤에 EOF가 나오지 않는 이상, 개행과 함께 뒷 문장까지 출력이 되버리는 결과가 나올 수 있다.

 

2) BUFFER_SIZE가 1문장의 길이보다 짧은 경우 

문장이 전부 출력되지 않고 잘린다. 다음 read()에서 이어서 읽힌다.

"I'm turnig, turnig, turnig,turnig, turnig around" 라는 문장이 4번째, 5번째, 6번째의 세 차례에 걸쳐 읽힌 것을 보면 알 수 있다.

 

3) 읽어온 부분에서 문장의 끝이 개행('\n')일 경우

문장에 이어서 개행까지 같이 출력되며, BUFFER_SIZE의 길이가 넉넉한 경우 다음 문장의 일부까지 출력될 수 있다.

 

4) 읽어온 부분에서 문장의 끝이 EOF 일 경우

BUFFER_SIZE가 넉넉하다고 하더라도, 남아있는 문장을 읽은 후에는 파일에 남아있는 것이 없으므로 더이상 읽지 않는다.

 

 

 

 


 

 

(6) get_next_line은 어떤 구조로 실행되야할까?

 

과제에서 제시한 get_next_line 함수의 프로토타입은 다음과 같다.

 

* PROTOTYPE

형식 : int get_next_line(int fd, char **line);

인자 :

          - int fd : 파일을 읽어오기 위한 파일 디스크립터

          - char **line : 읽어온 값

반환 :

          1 : 문장 1개를 읽고나서 반환

          0 : EOF까지 도달했을 때 반환

         -1 : 오류가 발생했을 때 반환

 

허용 함수 : read, malloc, free

설명 : fd로 부터 문장 하나를 읽고, 개행없이 그 문장을 반환해주는 함수를 작성하여라.

 

 

 

 

과제에서 제시한 고려사항을 충족하는 get_next_line을 구현할 경우, get_next_line을 반복문을 통해 실행하면 read()를 사용하여 1문장씩 받아와야 한다. 그리고 EOF에 도달하면 읽기를 중단해야 한다. 이때, get_next_line() 함수는 1, 0, -1 를 리턴해야한다.

따라서 이런 형식으로 함수를 실행할 수 있을 것이라 생각하고 main을 짜보았다.

참고로, get_next_line.h 헤더파일에는 read()를 위한 <unistd.h>와 get_next_line()속 malloc()을 위한 <stdlib.h> 헤더가 포함되어 있어야 한다.

 

get_next_line 메인 함수

- get_next_line()이 -1 을 리턴한 경우는 에러가 발생한 경우이므로 처리해주었다.

- get_next_line()이 1을 리턴한 경우는 문장 1개를 읽어왔다는 뜻이므로 받아온 문장을 출력해주고 계속 loop를 진행한다

- get_next_line()이 0을 리턴한 경우는, EOF에 도달했다는 뜻이므로 while문을 빠져나온다.

 

 

 

 

 


 

(7) gnl 함수의 역할과 구조 설계

 

 

get_next_line()을 통해 read()된 값에 대한 처리를 해줘서, 함수 호출 시 1 문장씩 (char *) 포인터에 받아올 수 있게 하는 것이 이 과제의 목표이다.

 

 

 

먼저, 0번째 출력에서 1개의 문장만 뽑아오려면, 받아온 문장에서 개행이 있을 경우, 개행과 그 뒷부분을 제외하고 문장 1개를 (char *) 포인터에 담아주면 된다. "i wonder how i wonder why(\n)yester"에서 "(\n)yester" 부분을 제외하고 "i wonder how i wonder why"로 만들어준다는 뜻이다.

 

 

그런데 이때, get_next_line()을 다시 실행하여 다음 문장을 받아오려는 경우 문제가 생긴다!

다음의 read()에서는 지난번의 read()로 읽어온 부분 이후부터 읽어오기 때문에, 앞서 읽혔던 "yester" 부분 없이 "day you told me ' bout the blue blue sky"밖에 읽어올 수가 없다. 문장의 일부를 잃어버리게 되는 셈이다.

그렇다면 어떻게 "yesterday you told me ' bout the blue blue sky" 라는 문장을 만들 수 있을까?

 

 

 

혹시, 저번에 get_next_line()을 실행했을 때  문장을 자르느라 남겨진 뒷부분의 "yester"를 어딘가에 저장해놓고, 이번에 get_next_line()에서 read된 문자열의 앞에 이걸 다시 붙여주면 어떨까? 그러면 문장의 일부를 잃어버리는 일을 방지할 수 있을 것 같다.

 

그런데 이런 경우, 전역함수는 과제에서 허용되지 않고, 지역 변수는 함수가 종료될 때마다 메모리가 소멸된다. 따라서 두가지 방법으로는 다음 번에 함수를 호출할 때 이번에 저장한 메모리를 이어서 사용할 수가 없다. 그럼 어떻게 해야할까? 

정답은 이번 과제의 핵심인 static 변수를 활용하는 것이다!

 

static 변수는 프로그램(main)이 종료되기 전까지 메모리가 소멸되지 않고 유지되는 특징을 가진다.

따라서 get_next_line()을 몇번 호출하던 간에, 저번에 함수를 호출했을 때 저장해놓은 값을 가져다 쓸 수 있다.

 

 

 

 

  이때, 저번에 남겨진 문자열을 저장해놓을 변수, static char *backup을 선언한다고 가정하자.

개행으로 구분된 문자열의 뒷부분을 backup이라는 변수에 저장해놓고 다음번 read된 문자열에 붙여주면, BUFFER_SIZE 가 문장의 길이보다 긴 경우 나타나는 문제를 해결할 수 있을 것이다.

 

예시 1

  만일 BUFFER_SIZE 가 문장의 길이보다 짧은 경우는 어떻게 해야할까? 이때는 한번의 read로 문장을 다 읽어들이지 못한다.

이때는 문장의 끝(개행이나 EOF)이 나올 때까지 read()를 반복적으로 수행하여, 반환한 문자열을 저장한 별도의 버퍼에 read()된 메모리를 연속적으로 저장해주면 될 것이다. (read의 버퍼는 매번 read할 때마다 새로운 값으로 채워지므로, 문장을 저장하는 데는 별도의 변수를 사용해야함)

 

예시 2

 

이때 결국 읽어온 문장의 끝이 개행('\n')이라면 개행을 기준으로 앞뒤로 잘라주고 (개행은 반환될 문자열에 포함시키지 않음), 반환한 문자열을 저장한 버퍼에 개행('\n') 전의 앞부분을 붙여주면 된다.

만일 읽어온 문장의 끝이 EOF라면 이번 read()로 읽어온 문자열 전체를 반환한 문자열을 저장한 버퍼에 붙여주면 된다.

 

 

 

 

마지막으로, 인자로 받은 문자열 포인터에, 반환할 문자열을 저장할 버퍼의 주소를 할당해주고 0, 1 중 하나를 리턴하면 마무리된다.

 

이때 읽어온 문장이 EOF라면 0을, 읽어온 문장이 EOF가 아니라면 1을 리턴하여 get_next_line() 함수를 종료해준다.

읽어온 문장이 EOF인지 처리해주기 위해서는, read()의 리턴값으로 받은 읽은 바이트 수가 0으로 나오는 경우를 처리해주면 된다.

 

 리턴값 중 -1은 오류가 발생하였을 때 리턴하는 것이므로, 함수 호출 시 line의 null 여부와 read할 BUFFER_SIZE 그리고 fd의 범위(0 ~ OPEN_MAX)에 대한 예외처리를 할 때 리턴하면 된다.

 

 

 

 

 

 

 

 

 

 

 

 

참고 자료 링크 : 

더보기

https://dev-ahn.tistory.com/96

 

리눅스 - 파일 디스크립터

File Descriptor (파일 디스크립터) [출처: http://dev.plusblog.co.kr/22] 1. 파일 디스크립터 - 시스템으로부터 할당 받은 파일을 대표하는 0이 아닌 정수 값 - 프로세스에서 열린 파일의 목록을 관리하는 테

dev-ahn.tistory.com

https://bubble-dev.tistory.com/entry/CC-open-%ED%95%A8%EC%88%98-%ED%8C%8C%EC%9D%BC-%EC%83%9D%EC%84%B1-%EC%9D%BD%EA%B8%B0-%EC%93%B0%EA%B8%B0

 

C/C++ open 함수 - 파일 생성 / 읽기 / 쓰기

Open 함수 기능 파일을 열거나 생성 후 열어주는 함수 함수원형 #include #include #include int open(const char *filepath, int flag); int open(const char *filepath, int flag, mode_t mode); 매개변수 const..

bubble-dev.tistory.com

https://jeongchul.tistory.com/368

 

리눅스 open - 리눅스 시스템 프로그래밍

리눅스 open - 리눅스 시스템 프로그래밍 open은 이미 존재하는 파일을 읽기 또는 쓰기용으로 열거나 새로운 파일을 생성하여 연다. #include 헤더 파일을 사용 int open(const char* pathname, int flags, [mode_..

jeongchul.tistory.com

https://badayak.com/4486

 

C언어 파일 읽기 함수 read()

C함수 파일 읽기 read() open() 함수로 열기를 한 파일의 내용을 읽기를 합니다. 헤더: unistd.h 형태: ssize_t read (int fd, void *buf, size_t nbytes) 인수: int fd 파일 디스크립터 void *buf 파일을 읽어 들..

badayak.com

https://mong9data.tistory.com/111

 

open, read, close 함수 정리

open 헤더 : fcntl.h open은 두 가지 형태의 시스템 콜을 가지고 있다. 형태는 아래와 같다. 참고로 rush 및 bsq에서는 첫 번째 시스템 콜을 이용할 것이다. int open(const char *pathname, int flags); int open..

mong9data.tistory.com

https://www.joinc.co.kr/w/man/2/lseek

 

linux man page : lseek - 파일의 위치를 재지정한다.

 

www.joinc.co.kr

http://blog.naver.com/PostView.nhn?blogId=rbdi3222&logNo=220732695910 

 

정적변수란?

전역 변수는 프로그램의 모든 영역에서 접근이 가능하고, 프로그램이 종료되지 않는 한 메모리가 소멸되지 ...

blog.naver.com

https://dojang.io/mod/page/view.php?id=690 

 

C 언어 코딩 도장: 79.2 정적 변수 선언하기

정적 변수를 알아보기 전에 먼저 자동 변수로 예제를 작성해보겠습니다. 다음 내용을 소스 코드 편집 창에 입력한 뒤 실행해보세요. variable.c #include void increaseNumber() { int num1 = 0; // 변수 선언 및

dojang.io

https://www.notion.so/Get-Next-Line-c7a311e63bd2483ab5bf404791e917c6

 

Get Next Line

단축 주소 : [ http://bit.ly/gnljs ]

www.notion.so

 

댓글()

[Libft] C 언어 라이브러리 구현_Part2_추가함수 구현3

IT/42Seoul|2021. 5. 23. 18:56

혹시나 문제가 된다면 바로 비공개 처리하겠습니다. 지적이나 댓글 환영합니다!

 

이번 포스팅에서는 이어서 part2의 함수를 구현해 보겠다. file descripter를 매개변수로 받아 write()를 사용하는 함수 4가지를 구현하였다.

주의사항이라고 적은 사항들이 대부분 내가 실수했으나 고쳐서 어떻게 해결했는 지에 대한 내용들이다.

 

참고로, 내가 정의한 libft.h 헤더에는 <unistd.h>와 <stdlib.h>가 include 되어있다. 따라서 libft.h를 호출하면, 따로 정의하지 않고도 <unistd.h> 에 정의된 size_t 타입과 <stdlib.h>의 malloc/free를 사용할 수 있다. 또한 <unistd.h>에 있는 write 함수 또한 사용할 수 있다.

 

 

 

* fd (File descripter)란 무엇인가?

1) 시스템으로부터 할당 받은 파일을 대표하는 0이 아닌 정수 값

2) 프로세스에서 특정 파일에 접근할 때 사용하는 추상적인 값

3) 프로세스에서 열린 파일의 목록을 관리하는 테이블의 인덱스

 

- 유닉스 시스템에서 모든 것은 "파일"이다. 모든 객체들은 파일로써 관리된다.

- 유닉스 시스템에서 프로세스가 이 파일들을 접근할 때에 파일 디스크립터라는 개념을 이용한다.

 

- 파일 디스크립터는 '0이 아닌 정수' 값이다. 즉, 음수가 아닌 0과 양수인 정수 값을 갖는다.

프로세스가 실행 중에 파일을 Open 하면 커널은 해당 프로세스의 파일 디스크립터 숫자 중에 사용하지 않는 가장 작은 값을 할당해 준다.

그 다음 프로세스가 열려있는 파일에 시스템 콜을 이용해서 접근할 때, FD 값을 이용해 값을 지칭할 수 있다.

 

프로그램이 프로세스로 메모리에서 실행을 시작할 때, 기본적으로 할당되는 파일 디스크립터들이 있다. 바로 표준 입력, 표준 출력, 표준 에러이다. 이들에게 각각 0, 1, 2 라는 정수가 할당된다.

0이 아닌 정수로 표현된느 파일 디스크립터는 0 ~ OPEN_MAX까지의 값을 가질 수 있으며, OPEN_MAX 값은 플랫폼에 따라 다르다.

 

이미지 출처 : http://itnovice1.blogspot.com/2019/08/linux-file-descriptor.html

파일 디스크립터는 위에서 볼 수 있듯이, 단순히 숫자인 이유는 프로세스가 유지하고 있는 FD 테이블의 인덱스이기 때문이다. FD 3번이라는 의미는 FD 테이블의 3번 항목이 가리키는 파일이라는 의미이다.

프로세스는 이런 FD 테이블과 파일 테이블의 정보를 직접 고칠 수 없으며, 반드시 커널을 통해서 수정해야 한다.

 

 

 

 

(1) ft_putchar_fd

: 주어진 fd에 대하여 주어진 문자를 write한다.

 


- 구현 코드 예시 :

1
2
3
4
5
6
#include "libft.h"
 
void    ft_putchar_fd(char c, int fd)
{
    write(fd, &c, 1);
}
cs

 

 

 

(2) ft_putstr_fd

: 주어진 fd에 대하여 주어진 문자열을 write한다.

 


- 구현 코드 예시 :

1
2
3
4
5
6
7
#include "libft.h"
 
void    ft_putstr_fd(char *s, int fd)
{
    write(fd, s, ft_strlen(s));
}
 
cs

- ft_strlen()을 통해 문자열의 길이를 구해서,  s의 시작주소로부터 그만큼의 바이트를 주어진 fd로 write한다.

 

 

 

(3) ft_putendl_fd

: 주어진 fd에 대하여 주어진 문자열을 개행문자와 함께 write한다.

 


- 구현 코드 예시 :

1
2
3
4
5
6
7
#include "libft.h"
 
void    ft_putendl_fd(char *s, int fd)
{
    ft_putstr_fd(s, fd);
    write(fd, "\n"1);
}
cs

- 주의사항 : 따로 개행을 write할 때, write(fd, "\n", 1)이 아니라 write(1, "\n", 1)라고, 습관적으로 fd 대신 1을 쓰는 실수를 할 수 있음

특히 이런 실수의 경우는 잘 눈에 띄지 않아 찾아내기가 어렵다

 

- 앞서 구현한 ft_putstr_fd()를 활용하여 문자열을  write한 다음, write함수를 통해 주어진 fd로 개행문자를 write해준다.

 

 

(4) ft_putnbr_fd

: 주어진 fd에 대하여 주어진 숫자를 write한다.

 


- 구현 코드 예시 :

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
26
#include "libft.h"
 
static void    write_nbr_fd(int n, int fd)
{
    char    c;
 
    c = (n % 10+ '0';
    if (n >= 10)
        write_nbr_fd(n / 10, fd);
    write(fd, &c, 1);
}
 
void        ft_putnbr_fd(int n, int fd)
{
    if (n == -2147483648)
    {
        write(fd, "-2147483648"11);
        return ;
    }
    else if (n < 0)
    {
        write(fd, "-"1);
        n *= -1;
    }
    write_nbr_fd(n, fd);
}
cs

- 주의사항 : 습관적으로 write()의 fd 인자 위치에 1을 써버리는 실수를 할 수 있다. 특히 "-"를 write() 해주는 부분을 주의해야한다

 

- 숫자 n을 양수로 만들어, 재귀를 통해 한 글자씩 write해주는 방식으로 구현하였다.

- 이때 int 최솟값인 -2^31에 -1을 곱해 양수로 만들면 int 범위 (최대값 =  2^31 - 1)를 벗어나므로, 이는 따로 앞부분에서 예외처리 해주었다.

- 음수에 (-1)을 곱해 양수로 만들 때, "-" 부호를 주어진 fd로 write() 해주었다.

 

 

 

 

 

참고 자료 출처 : 

댓글()