c라이브러리 내장 모듈들을 직접 구현해보며 내부에서 어떤 방식으로 돌아가고 작동하는지 최대한 의미를 생각하며 구현하는 과제
man페이지를 기반으로 코드를 짜는것이 중요하다!
라피신때 작성한 함수와 겹치는게 있지만, 다시한번 작성하는것을 추천!
43개나 되는 함수를 직접 짜야하기 때문에 의외로 긴 시간이 걸립니다.
내장 모듈을 쓰지않고 작성한 libft 파일들을 다음 과제에도 사용해야하기 때문에 꼼꼼하게 짜셔야 합니다.
int ft_atoi(const char *str)
문자열 str
을 int형으로 변환하는 함수
숫자로 표현할 수 있는 문자가 나오기 전까지 공백문자들을 모두 무시한 후 다음 주어지는 문자가 '+', '-' 또는 '0' ~ '9' 가 아니라면 변환하지 않는다.
atoi함수에 INT_MAX값 보다 큰 값을 넣었을경우 실제 내장 atoi와 작성한 ft_atoi와 값이 다르게 나올것입니다.
꼭 man페이지를 확인하시고 strtol에 대해서도 공부하시면 좋습니다.
void ft_bzero(void *dest, size_t size)
memset과 유사하지만 함수명에서 유추 할 수 있듯 0으로 사이즈만큼 채우는 함수
void *ft_calloc(size_t count, size_t size)
malloc과 같이 size
* count
만큼 연속적으로 할당하는 동적할당하고 포인터를 반환하는 함수지만, 할당된 메모리 값을 0바이트로 채워서 리턴합니다.
int ft_isalnum(int c)
int ft_isalpha(int c)
int ft_isascii(int c)
int ft_isdigit(int c)
int ft_isprint(int c)
int ft_tolower(int c)
int ft_toupper(int c)
아스키 값으로 들어온 인자들이 해당 함수의 조건에 맞는지 확인하는 함수
char *ft_itoa(int n)
atoi와 반대되는 함수
int n
을 인자로 받아 문자열로 변환하여 해당 값을 리턴하는 함수
문자열을 리턴하다보니 '-'와 '0'을 고려해야합니다.
atoi와는 달리 리턴되는 문자열 사이즈에 따라 동적할당을 해주는것이 포인트
void *ft_memchr(const void *dest, int ch, size_t size)
dest
가 가르키는 곳에서부터 size
바이트까지 ch
와 일치하는 값을 리턴하는 함수
ch
는 int형으로 인자를 받지만, 내부에서 unsigned char로 해석합니다.
int ft_memcmp(const void *ptr1, const void *ptr2, size_t size)
return (*ptr1 - *ptr2);
두 메모리를 size
바이트 까지 비교하며, 두 값이 같으면 0, ptr1
이 ptr2
보다 크면 양수 / 작으면 음수를 리턴
void *ft_memcpy(void *dest, const void *src, size_t size)
dest에 size바이트만큼 src가 가르키는 위치부터 복사합니다.
void *ft_memmove(void *dest, const void *src, size_t size)
{
size_t i;
unsigned char *dst;
const unsigned char *sc = (const unsigned char *)src;
i = 0;
dst = (unsigned char *)dest;
if (dest > src)
{
while (size--)
dst[size] = sc[size];
}
if (dest < src)
{
while (size--)
{
dst[i] = sc[i];
i++;
}
}
return (dest);
}
dest
가 가르키는곳으로 size
바이트만큼 src
이 가르키는곳을 옮기는 함수
버퍼를 이용하므로
dest
와src
가 겹쳐도 동작에는 이상이 없어야 하기 때문에 메모리 영역이 겹치는 부분에 대해 깊게 생각해야합니다.
void *ft_memset(void *dest, int value, size_t size)
{
unsigned char *dst;
dst = (unsigned char *)dest;
while (size--)
{
*dst = (unsigned char)value;
dst++;
}
return (dest);
}
dest
의 주소부터 value
값으로 size
바이트 수 만큼 채우는 함수
value
는 int로 인자를 받지만, 내부에서 unsigned char로 해석됩니다. 즉 int값으로 초기화가 불가능합니다.
void ft_putchar_fd(char c, int fd)
void ft_putendl_fd(char *s, int fd)
void ft_putnbr_fd(int n, int fd)
void ft_putstr_fd(char *s, int fd)
fd(파일 디스크립터)가 무엇을 의미하는지를 깊게 생각 해야합니다.
0 = 표준 입력 / 1 = 표준 출력 / 2 = 표준 에러
fd
값은 음수가 아닌 정수값을 가지며 0~2까지는 <unistd.h>에 명시 되어있습니다.
int wordlen(char const *s, char c)
{
int size;
int j;
j = 0;
size = 0;
while (*s)
{
while (*s && (*s == c))
s++;
if (*s && (*s != c))
{
while (*s && (*s != c))
s++;
j++;
}
}
return (j);
}
char cpy(char **sp, char const *start, char const *s, int j)
{
int len;
int k;
k = 0;
len = s - start;
sp[j] = (char *)malloc(sizeof(char) * (len + 1));
if (sp[j] == NULL)
{
while (--j)
free(sp[j]);
free(sp);
return (0);
}
while (k < len)
{
sp[j][k] = start[k];
k++;
}
sp[j][k] = 0;
return (1);
}
char **nom(char **sp, char const *s, char c)
{
char const *start;
int j;
j = 0;
while (*s)
{
while (*s && (*s == c))
s++;
start = s;
if (*s && (*s != c))
{
while (*s && (*s != c))
s++;
if (cpy(sp, start, s, j) == 0)
return (0);
j++;
}
}
sp[j] = 0;
return (sp);
}
char **ft_split(char const *s, char c)
{
char **sp;
if (s == 0)
return (0);
sp = (char **)malloc(sizeof(char *) * (wordlen(s, c) + 1));
if (sp == NULL)
return (0);
return (nom(sp, s, c));
}
}
파이썬에서는 .split() 내장함수를 쓰면 해당기능을 바로 사용할 수 있지만, c/c++에는 없기때문에 만들어줘야 합니다.
s
가 가르키는 문자열을 char c
를 기준으로 나누어 리턴하는 함수
이중배열로 작성하여 배열의 마지막에는 NULL
값 삽입
메모리 할당을 실패하여 함수를 종료할 때,
메모리 누수를 방지하기 위해 배열 칸마다 free 후 배열 전체를 free해줘야 합니다.
char *ft_strchr(const char *s, int c)
s
가 가리키는 문자열에서 첫 번째 c
를 찾습니다.
c
는 검색할 문자를 의미하고 int로 인자를 받지만, 함수 내부에서 char로 해석됩니다.
마지막 NULL
문자도 문자열의 일부로 간주하기 때문에 문자열의 맨 끝 부분을 가리키는 포인터를 얻기 위해 사용할 수도 있습니다.
char *ft_strdup(const char *s1)
s1
을 복제한 문자열을 리턴하는 함수
void ft_striteri(char *s, void (*f)(unsigned int, char *))
문자열 s
의 각 요소들을 함수 f
에 적용시키는 함수
함수포인터에 대해 생각해보고, 함수 f
에는 문자열 s
의 요소들의 인덱스와 주소값을 전달해줍니다.
char *ft_strjoin(char const *s1, char const *s2)
문자열 s1
과 s2
를 이어붙인 문자열을 리턴하는 함수.
size_t ft_strlcat(char *dest, const char *src, size_t destsize)
dest
의 마지막 위치에 size - strlen(dest) - 1
만큼 복사하고 끝에 NULL
값을 삽입합니다.
size
가 dest
의 크기보다 작을 때, strlen(src) + size를 리턴합니다.
size
가 dest
의 크기보다 클 때, strlen(src) + strlen(dest)를 리턴합니다.
size_t ft_strlcpy(char *dest, const char *src, size_t size)
dest
에 src
를 size
바이트 만큼 복사하는 함수로 src
의 길이를 리턴합니다.
원리는 strncpy와 비슷하지만 strncpy함수보다 오류가 적은 함수
strncpy의 경우 NULL
값 삽입을 보장하지 않습니다.
strlcpy는 size
가 0이 아닌 경우 size - 1
까지 복사를 진행하고 마지막에 NULL
값을 삽입해줍니다.
size_t ft_strlen(const char *s)
문자열 s
의 길이를 리턴합니다.
char *ft_strmapi(char const *s, char (*f)(unsigned int, char))
문자열 s
의 각 요소에 함수 f
에 적용한 값들로 새로운 하나의 문자열 만들고 문자열의 주소값을 반환하는 함수
striteri와 유사하지만, 함수 f
가 받는 인자의 형태와 리턴값에 대해 고려해야 합니다.
int ft_strncmp(const char *s1, const char *s2, size_t n)
문자열 s1
과 s2
를 n
개 만큼 비교합니다.
ft_memcmp와 차이는 ft_memcmp은 메모리 비교 함수라 '\0'을 만나도 계속 진행합니다.
char *ft_strnstr(const char *haystack, const char *needle, size_t len)
문자열 haystack
에서 NULL
로 끝나는 문자열 needle
의 첫 번째 부분을 찾습니다. 여기서 haystack
에서 len
바이트 만큼 탐색합니다.
NULL
문자 뒤에 나타나는 문자는 검색되지 않습니다.
needle
값이 비어 있으면 haystack
을 반환합니다.haystack
문자열에서 needle
문자열을 찾지 못하면 NULL
을 반환합니다.needle
문자열을 찾으면, haystack
에서 needle
문자열 시작 부분 위치 주소를 반환합니다.char *ft_strrchr(const char *s, int c)
s
가 가리키는 문자열에서 c
를 찾습니다.
c
는 검색할 문자를 의미하고 int로 전달되지만, 내부에서는 char로 해석됩니다.
마지막 NULL
문자도 문자열의 일부로 간주하기 때문에 이 함수는 문자열의 맨 끝 부분을 가리키는 포인터를 얻기 위해 사용할 수도 있습니다.
strchr과 동일하게 문자열
s
에c
가 있을경우 해당 위치 문자열s
의 포인터를 리턴하는 함수지만, 함수명에서 유추할 수 있듯 reverse로 문자열의 뒷 부분부터 탐색하는 함수입니다.
char *ft_strtrim(char const *s1, char const *set)
{
int s;
int e;
int i;
char *sc;
if (s1 == 0)
return (0);
i = 0;
s = 0;
e = ft_strlen(s1);
while (s1[s] && check(s1[s], set))
s++;
while (e > s && check(s1[e - 1], set))
e--;
sc = (char *)malloc(sizeof(char) * (e - s + 1));
if (sc == NULL)
return (0);
while (e > s)
{
sc[i] = s1[s];
i++;
s++;
}
sc[i] = '\0';
return (sc);
}
s1
의 왼쪽에서 set
이 아닌 문자가 나올 때부터 s1
의 오른쪽에서 set
이 아닌 문자가 나올 때까지 자르는 함수
char *ft_substr(char const *s, unsigned int start, size_t len)
{
size_t i;
size_t size;
char *sc;
if (s == 0)
return (0);
size = ft_strlen(s);
i = 0;
if (size < start)
len = 0;
else if (len > size - start)
len = size - start;
sc = (char *)malloc(sizeof(char) * (len + 1));
if (sc == NULL)
return (0);
while (len--)
{
sc[i] = s[start];
start++;
i++;
}
sc[i] = '\0';
return (sc);
}
문자열 s
의 start
위치부터 len
길이 만큼 자른 문자열을 리턴하는 함수
s
의 길이와 start
의 크기와 len
의 크기에 따른 다양한 예외처리를 주의
Mandatory Part는 표준 C라이브러리 함수와 메모리와 문자열을 처리 함수였다면, Bonus Part는 리스트를 다루는 함수들을 만드는 과제입니다.
typedef struct s_list
{
void *content;
struct s_list *next;
} t_list;
구조체안에 리스트에 들어갈 content와 다음 노드를 가르키는 next를 선언 해줍니다.
t_list *ft_lstnew(void *content)
{
t_list *new;
new = (void *)malloc(sizeof(t_list));
if (new == NULL)
return (0);
new->content = content;
new->next = NULL;
return (new);
}
구조체 사이즈 만큼 동적 할당 후 인자로 받은 content
는 새 노드의 content
로 받고, next
를 가르키는 값은 NULL
값을 넣어 연결되어있지 않은 독립된 새 노드를 생성하는 함수
int ft_lstsize(t_list *lst)
리스트 lst의 길이를 리턴합니다.
void ft_lstadd_front(t_list **lst, t_list *new)
void ft_lstadd_back(t_list **lst, t_list *new)
리스트의 맨 앞과 맨 뒤에 새 노드를 추가하는 함수
t_list **lst
: t_list 포인터의 주소를 가리키는 변수
t_list *new
: lst의 맨 앞 또는 맨 뒤에 추가할 노드의 주소를 가리키는 포인터 변수
t_list *ft_lstlast(t_list *lst)
리스트의 맨 뒤의 노드를 리턴하는 함수
void ft_lstdelone(t_list *lst, void (*del)(void *))
{
if (lst == 0 || del == 0)
return ;
del(lst->content);
free(lst);
}
t_list *lst
의 content
를 del()
함수를 통해 삭제 후 lst
를 free하는 함수
함수명에서 유추할 수 있듯이 노드 하나를 해제하며, next값은 건드리면 안된다.
void ft_lstclear(t_list **lst, void (*del)(void *))
{
t_list *head;
if (*lst == 0 || del == 0)
return ;
while (*lst)
{
head = (*lst)->next;
del((*lst)->content);
free(*lst);
*lst = head;
}
}
리스트 내부 노드들을 전부 del()
함수를 통해 삭제후 free하는 함수
while (*lst)
{
head = (*lst)->next;
ft_lstdelone(*lst, del);
*lst = head;
}
위에 사용한 ft_lstdelone함수를 활용하여 위와 같이 작성할 수도 있다.
void ft_lstiter(t_list *lst, void (*f)(void *))
lst->content
에 f()
함수를 적용하는 함수
t_list *ft_lstmap(t_list *lst, void *(*f)(void *), void (*del)(void *))
{
t_list *newnode;
t_list *node;
node = 0;
while (lst)
{
newnode = ft_lstnew((*f)(lst->content));
if (newnode == 0)
{
ft_lstclear(&node, del);
return (0);
}
ft_lstadd_back(&node, newnode);
lst = lst->next;
}
return (node);
}
지금껏 만들었던 리스트 관련 함수를 활용하여 새 리스트를 만드는 함수
기존 리스트의 content
를 f()
함수를 적용하여 만든 새 노드를 만들고 할당에 실패하면 ft_lstclear 를 통해 모든 lst
와 lst->content
를 삭제 후 free 해줍니다.
과거 1학년 c언어를 배웠을 당시 <string.h> 내장 함수에 있던 strcmp나 strcpy 등 당시에만 암기로 외우고 내부에선 어떻게 동작하는지도 모르고 사용했었습니다.
내부 동작을 모르고 암기하니 금방 잊어버려 시험 때 함수명을 까먹고 문제를 못 풀었던 기억이 나네요.
c 라이브러리 내장 함수들을 직접 하나하나 코딩하니 3개월 전에 작성했던 libft인데도 잊혀지지 않고 이젠 함수명만 봐도 어떻게 동작하는지 바로 파악이 됩니다.
혹여나 잊었다 해도 내부 동작에 대해 완벽하게 파악하고 있으니 바로 라이브 코딩이 가능하고 지금 다시 해당 함수들을 작성한다면 더 깔끔하고 간결하게 작성할 수 있다고 생각이 되는 것이 많이 성장했다고 느낍니다.
그 외 해당 과제를 해결하기 위한 선지식 공부 내용은 노션에 정리해 두었습니다.
포인터에 대한 개념을 이중포인터도 활용하고 함수포인터도 활용함으로써 더 확실하게 이해할 수 있었고, 특히 예전엔 리스트의 동작이 잘 이해가지 않았는데 이젠 자유자재로 활용할 수 있을 정도로 성장했습니다.