libft - c라이브러리 구현하기

yeham·2022년 10월 27일
1

42Seoul

목록 보기
1/18
post-thumbnail

들어가기에 앞서

  • c라이브러리 내장 모듈들을 직접 구현해보며 내부에서 어떤 방식으로 돌아가고 작동하는지 최대한 의미를 생각하며 구현하는 과제

  • man페이지를 기반으로 코드를 짜는것이 중요하다!

  • 라피신때 작성한 함수와 겹치는게 있지만, 다시한번 작성하는것을 추천!

  • 43개나 되는 함수를 직접 짜야하기 때문에 의외로 긴 시간이 걸립니다.

  • 내장 모듈을 쓰지않고 작성한 libft 파일들을 다음 과제에도 사용해야하기 때문에 꼼꼼하게 짜셔야 합니다.

🖥️ Mandatory Part

ft_atoi

int	ft_atoi(const char *str)

문자열 str을 int형으로 변환하는 함수
숫자로 표현할 수 있는 문자가 나오기 전까지 공백문자들을 모두 무시한 후 다음 주어지는 문자가 '+', '-' 또는 '0' ~ '9' 가 아니라면 변환하지 않는다.

atoi함수에 INT_MAX값 보다 큰 값을 넣었을경우 실제 내장 atoi와 작성한 ft_atoi와 값이 다르게 나올것입니다.
꼭 man페이지를 확인하시고 strtol에 대해서도 공부하시면 좋습니다.

ft_bzero

void	ft_bzero(void *dest, size_t size)

memset과 유사하지만 함수명에서 유추 할 수 있듯 0으로 사이즈만큼 채우는 함수

ft_calloc

void	*ft_calloc(size_t count, size_t size)

malloc과 같이 size * count만큼 연속적으로 할당하는 동적할당하고 포인터를 반환하는 함수지만, 할당된 메모리 값을 0바이트로 채워서 리턴합니다.

ft_isalnum / ft_isalpha / ft_isascii / ft_digit / ft_isprint / ft_tolower / ft_toupper

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)

아스키 값으로 들어온 인자들이 해당 함수의 조건에 맞는지 확인하는 함수

ft_itoa

char	*ft_itoa(int n)

atoi와 반대되는 함수
int n을 인자로 받아 문자열로 변환하여 해당 값을 리턴하는 함수

문자열을 리턴하다보니 '-'와 '0'을 고려해야합니다.
atoi와는 달리 리턴되는 문자열 사이즈에 따라 동적할당을 해주는것이 포인트

ft_memchr

void	*ft_memchr(const void *dest, int ch, size_t size)

dest가 가르키는 곳에서부터 size바이트까지 ch와 일치하는 값을 리턴하는 함수
ch는 int형으로 인자를 받지만, 내부에서 unsigned char로 해석합니다.

ft_memcmp

int	ft_memcmp(const void *ptr1, const void *ptr2, size_t size)

return (*ptr1 - *ptr2);

두 메모리를 size바이트 까지 비교하며, 두 값이 같으면 0, ptr1ptr2 보다 크면 양수 / 작으면 음수를 리턴

ft_memcpy

void	*ft_memcpy(void *dest, const void *src, size_t size)

dest에 size바이트만큼 src가 가르키는 위치부터 복사합니다.

ft_memmove

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이 가르키는곳을 옮기는 함수

버퍼를 이용하므로 destsrc가 겹쳐도 동작에는 이상이 없어야 하기 때문에 메모리 영역이 겹치는 부분에 대해 깊게 생각해야합니다.

ft_memset

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값으로 초기화가 불가능합니다.

ft_putchar_fd / ft_putendl_fd / ft_putnbr_fd / ft_putstr_fd

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>에 명시 되어있습니다.

ft_split

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해줘야 합니다.

ft_strchr

char	*ft_strchr(const char *s, int c)

s가 가리키는 문자열에서 첫 번째 c를 찾습니다.
c는 검색할 문자를 의미하고 int로 인자를 받지만, 함수 내부에서 char로 해석됩니다.

마지막 NULL 문자도 문자열의 일부로 간주하기 때문에 문자열의 맨 끝 부분을 가리키는 포인터를 얻기 위해 사용할 수도 있습니다.

ft_strdup

char	*ft_strdup(const char *s1)

s1을 복제한 문자열을 리턴하는 함수

ft_striteri

void	ft_striteri(char *s, void (*f)(unsigned int, char *))

문자열 s의 각 요소들을 함수 f에 적용시키는 함수
함수포인터에 대해 생각해보고, 함수 f에는 문자열 s의 요소들의 인덱스와 주소값을 전달해줍니다.

ft_strjoin

char	*ft_strjoin(char const *s1, char const *s2)

문자열 s1s2를 이어붙인 문자열을 리턴하는 함수.

ft_strlcat

size_t	ft_strlcat(char *dest, const char *src, size_t destsize)

dest의 마지막 위치에 size - strlen(dest) - 1 만큼 복사하고 끝에 NULL값을 삽입합니다.

sizedest의 크기보다 작을 때, strlen(src) + size를 리턴합니다.
sizedest의 크기보다 클 때, strlen(src) + strlen(dest)를 리턴합니다.

ft_strlcpy

size_t	ft_strlcpy(char *dest, const char *src, size_t size)

destsrcsize바이트 만큼 복사하는 함수로 src의 길이를 리턴합니다.

원리는 strncpy와 비슷하지만 strncpy함수보다 오류가 적은 함수
strncpy의 경우 NULL값 삽입을 보장하지 않습니다.
strlcpy는 size가 0이 아닌 경우 size - 1 까지 복사를 진행하고 마지막에 NULL값을 삽입해줍니다.

ft_strlen

size_t	ft_strlen(const char *s)

문자열 s의 길이를 리턴합니다.

ft_strmapi

char	*ft_strmapi(char const *s, char (*f)(unsigned int, char))

문자열 s의 각 요소에 함수 f에 적용한 값들로 새로운 하나의 문자열 만들고 문자열의 주소값을 반환하는 함수
striteri와 유사하지만, 함수 f가 받는 인자의 형태와 리턴값에 대해 고려해야 합니다.

ft_strncmp

int	ft_strncmp(const char *s1, const char *s2, size_t n)

문자열 s1s2n개 만큼 비교합니다.
ft_memcmp와 차이는 ft_memcmp은 메모리 비교 함수라 '\0'을 만나도 계속 진행합니다.

ft_strnstr

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 문자열 시작 부분 위치 주소를 반환합니다.

ft_strrchr

char	*ft_strrchr(const char *s, int c)

s가 가리키는 문자열에서 c를 찾습니다.
c는 검색할 문자를 의미하고 int로 전달되지만, 내부에서는 char로 해석됩니다.

마지막 NULL 문자도 문자열의 일부로 간주하기 때문에 이 함수는 문자열의 맨 끝 부분을 가리키는 포인터를 얻기 위해 사용할 수도 있습니다.

strchr과 동일하게 문자열 sc가 있을경우 해당 위치 문자열 s의 포인터를 리턴하는 함수지만, 함수명에서 유추할 수 있듯 reverse로 문자열의 뒷 부분부터 탐색하는 함수입니다.

ft_strtrim

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이 아닌 문자가 나올 때까지 자르는 함수

ft_substr

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);
}

문자열 sstart 위치부터 len길이 만큼 자른 문자열을 리턴하는 함수

s의 길이와 start의 크기와 len의 크기에 따른 다양한 예외처리를 주의

💻 Bonus Part

Mandatory Part는 표준 C라이브러리 함수와 메모리와 문자열을 처리 함수였다면, Bonus Part는 리스트를 다루는 함수들을 만드는 과제입니다.

typedef struct s_list
{
	void				*content;
	struct s_list		*next;
}	t_list;

구조체안에 리스트에 들어갈 content와 다음 노드를 가르키는 next를 선언 해줍니다.

ft_lstnew

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값을 넣어 연결되어있지 않은 독립된 새 노드를 생성하는 함수

ft_lstsize

int	ft_lstsize(t_list *lst)

리스트 lst의 길이를 리턴합니다.

ft_lstadd_front / ft_lstadd_back

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의 맨 앞 또는 맨 뒤에 추가할 노드의 주소를 가리키는 포인터 변수

ft_lstlast

t_list	*ft_lstlast(t_list *lst)

리스트의 맨 뒤의 노드를 리턴하는 함수

ft_lstdelone

void	ft_lstdelone(t_list *lst, void (*del)(void *))
{
	if (lst == 0 || del == 0)
		return ;
	del(lst->content);
	free(lst);
}

t_list *lstcontentdel()함수를 통해 삭제 후 lst를 free하는 함수
함수명에서 유추할 수 있듯이 노드 하나를 해제하며, next값은 건드리면 안된다.

ft_lstclear

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함수를 활용하여 위와 같이 작성할 수도 있다.

ft_lstiter

void	ft_lstiter(t_list *lst, void (*f)(void *))

lst->contentf()함수를 적용하는 함수

ft_lstmap

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);
}

지금껏 만들었던 리스트 관련 함수를 활용하여 새 리스트를 만드는 함수

기존 리스트의 contentf()함수를 적용하여 만든 새 노드를 만들고 할당에 실패하면 ft_lstclear 를 통해 모든 lstlst->content를 삭제 후 free 해줍니다.

✅ 배운점

과거 1학년 c언어를 배웠을 당시 <string.h> 내장 함수에 있던 strcmp나 strcpy 등 당시에만 암기로 외우고 내부에선 어떻게 동작하는지도 모르고 사용했었습니다.
내부 동작을 모르고 암기하니 금방 잊어버려 시험 때 함수명을 까먹고 문제를 못 풀었던 기억이 나네요.

c 라이브러리 내장 함수들을 직접 하나하나 코딩하니 3개월 전에 작성했던 libft인데도 잊혀지지 않고 이젠 함수명만 봐도 어떻게 동작하는지 바로 파악이 됩니다.
혹여나 잊었다 해도 내부 동작에 대해 완벽하게 파악하고 있으니 바로 라이브 코딩이 가능하고 지금 다시 해당 함수들을 작성한다면 더 깔끔하고 간결하게 작성할 수 있다고 생각이 되는 것이 많이 성장했다고 느낍니다.

그 외 해당 과제를 해결하기 위한 선지식 공부 내용은 노션에 정리해 두었습니다.

libft 선지식 공부 내용

포인터에 대한 개념을 이중포인터도 활용하고 함수포인터도 활용함으로써 더 확실하게 이해할 수 있었고, 특히 예전엔 리스트의 동작이 잘 이해가지 않았는데 이젠 자유자재로 활용할 수 있을 정도로 성장했습니다.

https://github.com/zerowin96/my_c_library

profile
정통과 / 정처기 & 정통기 / 42seoul 7기 Cardet / 임베디드 SW 개발자

0개의 댓글