프로젝트 소개

libft는 42서울의 첫번째 과제입니다. ft라는 것은 42서울에서의 과제명이나 함수명에 붙고는하는데요 저희가 다시 재구성했다는 의미를 담은 것 같습니다.

그래서 libft라는 것은 C 언어를 이용하여 프로그래밍을 할 때 자주 쓰이는 기본적인 함수들을 저희가 직접 구현해보는 프로젝트입니다.

그럼으로써 평상 시에 쓰던 함수들이 어떤 식으로 작동하는지, 어떤 것들을 신경써서 구현해야하는 지 이해할 수 있었습니다.

프로젝트 진행

이 프로젝트 같은 경우에는 특별히 하나의 목표를 달성하기 위한 코드를 작성해가는 것이 아니라 atoi, memset, bzero, calloc 등 하나하나의 함수를 구현해보는 연습이 주가 되었습니다.

그래서 기억에 남는 함수 몇개를 통해 어떤 것들을 배웠고 어떻게 제가 프로젝트를 진행했는지 소개해드리겠습니다.

1. void *ft_memset(void *b, int c, size_t len)

배운 점

알고리즘을 풀 때 한번쯤 memset을 써보신 경험이 있을 것입니다. 그런데 내가 원하는 값으로 초기화를 시켜준다길래 배열을 3으로 초기화 시키기 위해 memset(arr, 3, sizeof(arr)) 이런 식으로 넣어주게 되면 배열이 3이 아닌 이상한 값을 초기화되는 것을 본 적 있으실 것입니다.

이는 memset이 구현된 방식때문에 그렇습니다. memset은 초기화시킬 값으로 int를 받지만 내부적으로 그것을 unsigned char로 변환시킵니다. 또한 인자로 받은 배열을 한 바이트씩 초기화시킵니다.

그렇기때문에 4바이트였던 int에서 1바이트인 char로 변환되어도 값이 바뀌지않는 0, -1을 memset의 인자로 주었을 때만 정상적으로 배열이 초기화되는 것입니다.

이를 바탕으로 ft_memset을 다음과 같은 간단하게 구현해보았습니다.

코드

#include <stdlib.h>

void	*ft_memset(void *b, int c, size_t len)
{
	size_t			i;
	unsigned char	value;
	unsigned char	*str;

	value = (unsigned char) c;
	str = (unsigned char *) b;
	i = 0;
	while (i < len)
	{
		str[i++] = value;
	}
	return (str);
}

2. char **ft_split(char const *str, char c)

배운 점

이번에는 두번째 인자를 delimeter 삼아 첫번째 인자인 str을 스플릿하는 함수를 작성해보았습니다. 이 함수를 작성하면서 느꼈던 것은 라이브러리 안에 들어갈 함수를 구성할 때 static 함수를 사용하는 것이 유용하다는 것입니다.

라이브러리에는 많은 양의 함수들이 들어가게 될 것이고 그렇다보면 의도치않게 이름이 겹치는 함수가 생기게될 수도 있습니다. 그런데 static으로 함수를 정의하여 그것이 정의된 파일 내부에서만 인식되게한다면 이런 상황을 막을 수 있을 것입니다.

코드

#include "libft.h"

static int	word_count(const char *str, char c)
{
	int	count;
	int	word_finder;

	count = 0;
	word_finder = 1;
	while (*str)
	{
		if (word_finder && *str != c)
		{
			count++;
			word_finder = 0;
		}
		if (*str == c)
			word_finder = 1;
		str++;
	}
	return (count);
}

static char	*make_str(const char *str, char c)
{
	int		len;
	int		idx;
	char	*word;

	len = 0;
	while (str[len] != c && str[len] != '\0')
	{
		len++;
	}
	word = (char *)malloc(sizeof(char) * len + sizeof(char));
	if (word == 0)
		return (0);
	idx = -1;
	while (++idx < len)
	{
		word[idx] = str[idx];
	}
	word[idx] = '\0';
	return (word);
}

static void	free_all(char **arr, int arr_idx)
{
	int	i;

	i = 0;
	while (i < arr_idx)
	{
		free(arr[i++]);
	}
	free(arr);
}

char	**ft_split(char const *str, char c)
{
	int		word_finder;
	int		arr_idx;
	char	**arr;

	word_finder = 1;
	arr_idx = 0;
	arr = (char **)ft_calloc(word_count(str, c) + 1, sizeof(char *));
	if (arr == 0)
		return (0);
	while (*str)
	{
		if (word_finder-- == 1 && *str != c)
		{
			arr[arr_idx] = make_str(str, c);
			if (arr[arr_idx++] == 0)
			{
				free_all(arr, arr_idx - 1);
				return (0);
			}
		}
		if (*str == c)
			word_finder = 1;
		str++;
	}
	return (arr);
}

3. void *ft_memcpy(void *dst, const void *src, size_t n)

배운 점

다음은 memcpy 함수입니다. memcpy함수를 구현하면서 공부했던 것은 void *의 유용성이었습니다. 어떤 형식의 데이터를 주든 상관없이 값을 복사해주는 memcpy가 어떻게 일어나는지 신기했습니다. 그런데 그 비밀은 알고보니 단순했습니다.

int *는 그것이 가리키는 주소로 가서 int데이터의 사이즈 만큼 데이터를 읽어오기에 int값을 받아올 수 있는 것이었고 char *는 그것이 가리키는 주소로 가서 char데이터의 사이즈만큼 데이터를 읽어오기에 char값을 받아올 수 있는 것이었습니다.

void *는 이와 달리 몇칸만큼 읽어올지 정해져있지않은 포인터이고 그렇기에 얼마만큼 읽을지 캐스팅을 통해 정할 수 있는 것이었습니다.

그래서 어떤 타입을 인자로 넘기던지 memcpy 내부에서는 void *로 받은 인자를 char *로 해석해서 한 바이트씩 정보를 복사하기에 값이 정확하게 복사될 수 있었던 것이었습니다.

코드

#include <stdlib.h>

void	*ft_memcpy(void *dst, const void *src, size_t n)
{
	size_t	i;

	i = 0;
	if (dst == 0 && src == 0)
		return (dst);
	while (i < n)
	{
		((char *)dst)[i] = ((char *)src)[i];
		i++;
	}
	return (dst);
}

느낀 점

이 프로젝트를 하면서 느꼈던 것은 c 프로그래밍을 하면서 사용하던 기본적인 종류의 함수들이 내부적으로 구현되있는 방식을 이해하니 그 함수의 제약사항, 장점을 이해하는데 매우 도움이 되었다는 것이었습니다. 그래서 처음 보는 함수들은 그 동작뿐만이 아니라 구현 사항을 이해해야 에러없이 활용할 수 있겠다 생각이 들었습니다.