0. 시작하기 전에..
첫 포스트 작성이다. 다소 오래 전에 했던 과제라서 기억이 가물가물 하지만 일단 써보겠다.
쓰다 보니 어지간히 길어서 1, 2편으로 나눠야겠다.
1. 과제
Mandatory part
Category | Content |
---|---|
Program name | libft.a |
Turn in files | Makefile, ft_*.c |
Makefile | NAME, all, clean, fclean, re |
Libft authorized | n/a |
Description | Write your own library: a collection of functions that will be a useful tool for your cursus. |
42서울을 하면서 자주 쓸 함수들을 직접 구현하는 과제이다. 과제 진행 당시 주변에서 Libft는 자주 쓰니까 잘 만들어놓으면 좋다는 말을 많이 들었다. 또 기억나는 것이, 함수들을 구현할 때 앞서 만든 함수들을 이후에 만드는 함수에 사용할 수 있었던 구조였다.
Makefile
아마 이 당시에 Makefile 작성하는데 뭐가 뭔지 하나도 모르겠던 기억이 난다. 또 기억나는게 make bonus를 입력했을 때 어떻게 해야 보너스파일까지 컴파일하는지 고민했던 것 같다. 결과적으로 if문을 통해 flag를 줘서 해결했었던 것 같다.
첫 포스팅이니까 Makefile은 이 포스트에만 올리는 것으로 하겠다. 어차피 뒤에 다 똑같다. 나중에 가면 Makefile도 꾸며보고 싶어서 점점 발전했는데, 뭔가 변경사항이 있으면 다시 올리도록 하겠다.
Makefile
NAME = libft.a
SRCS = ft_isalpha.c \
ft_isalnum.c \
ft_isascii.c \
ft_isdigit.c \
ft_memset.c \
ft_toupper.c \
ft_tolower.c \
ft_strchr.c \
ft_strrchr.c \
ft_isprint.c \
ft_strlen.c \
ft_strncmp.c \
ft_memchr.c \
ft_bzero.c \
ft_memcpy.c \
ft_memmove.c \
ft_memcmp.c \
ft_strlcpy.c \
ft_strlcat.c \
ft_strnstr.c \
ft_atoi.c \
ft_calloc.c \
ft_strdup.c \
ft_substr.c \
ft_strjoin.c \
ft_strtrim.c \
ft_split.c \
ft_itoa.c \
ft_strmapi.c \
ft_striteri.c \
ft_putchar_fd.c \
ft_putstr_fd.c \
ft_putendl_fd.c \
ft_putnbr_fd.c
BONUSSRCS = ft_lstnew.c \
ft_lstadd_front.c \
ft_lstsize.c \
ft_lstlast.c \
ft_lstadd_back.c \
ft_lstdelone.c \
ft_lstclear.c \
ft_lstiter.c \
ft_lstmap.c
OBJS = ${SRCS:.c=.o}
BONUSOBJS = ${BONUSSRCS:.c=.o}
ifdef BONUSFLAG
OBJSALL = ${OBJS} ${BONUSOBJS}
else
OBJSALL = ${OBJS}
endif
CC = cc
CFLAGS = -Wall -Wextra -Werror
RM = rm -rf
all: $(NAME)
%.o: %.c
$(CC) $(CFLAGS) -c $^ -o $@
$(NAME): $(OBJSALL)
ar -rc $@ $^
bonus:
make BONUSFLAG=1 all
clean:
${RM} ${OBJS} ${BONUSOBJS}
fclean: clean
${RM} ${NAME}
re: fclean all
.PHONY: clean fclean re all bonus
지금보니까 진짜 원초적인 Makefile이다. 추억이라면 추억인 듯. 그럼 이제 과제로 들어가보자.
42서울은 더럽게 불친절하다. 알아서 어떤 함수인지 찾아야 한다. 평가를 받을 때 평가자분들이 man 명령어와 함께 살펴보셨던 기억이 있다.
isalpha
알파벳인지 확인하는 함수이다.
ft_isalpha.c
int ft_isalpha(int c)
{
if ((97 <= c && c <= 122) || (65 <= c && c <= 90))
return (1);
return (0);
}
isdigit
숫자인지 확인하는 함수이다.
ft_isdigit.c
int ft_isdigit(int c)
{
if (48 <= c && c <= 57)
return (1);
return (0);
}
isalnum
알파벳 혹은 숫자인지 확인하는 함수이다.
ft_isalnum.c
static int ft_isalpha_clone(int c);
static int ft_isdigit_clone(int c);
static int ft_isalpha_clone(int c)
{
if ((97 <= c && c <= 122) || (65 <= c && c <= 90))
return (1);
return (0);
}
static int ft_isdigit_clone(int c)
{
if (48 <= c && c <= 57)
return (1);
return (0);
}
int ft_isalnum(int c)
{
if (ft_isalpha_clone(c) || ft_isdigit_clone(c))
return (1);
return (0);
}
왜 이렇게 구현했냐고 생각이 들겠지만 그러려니 하고 넘어가면 된다. 라 피신 끝난 지도 얼마 안되었을 때다.
앞서 말했던 이전에 구현하는 함수들을 이후에 사용하는 구조라는 것이 이런 느낌이다.
isascii
ascii 문자인지 확인하는 함수이다.
ft_isascii.c
int ft_isascii(int c)
{
if (0 <= c && c <= 127)
return (1);
return (0);
}
isprint
출력 가능한 문자인지 확인하는 함수이다. ascii 표를 확인하면 대충 출력가능해 보이는 것들이 있다.
ft_isprint.c
int ft_isprint(int c)
{
if (32 <= c && c <= 126)
return (1);
return (0);
}
strlen
라 피신에서 징하게 썼겠지만 문자열의 길이를 리턴하는 함수이다.
ft_strlen.c
size_t ft_strlen(const char *s)
{
size_t count;
count = 0;
while (*s)
{
count++;
s++;
}
return (count);
}
memset
특정 메모리를 특정 값으로 초기화하는 함수이다. 동적 할당된 메모리 같은 것들을 한 번에 초기화할 때 유용하다.
ft_memset.c
void *ft_memset(void *b, int c, size_t len)
{
size_t i;
unsigned char *addr;
i = 0;
addr = (unsigned char *)b;
while (i < len)
{
*addr++ = c;
i++;
}
return ((void *)b);
}
bzero
특정 메모리를 0으로 초기화하는 함수이다. 이전에 쓴 함수를 가져오면 될 것 같다.
ft_bzero.c
void ft_bzero(void *s, size_t n)
{
ft_memset(s, 0, n);
}
memmove
메모리를 이동시키는 함수이다. 아마 src, dst관련해서 경우의 수를 좀 따져봐야 했던 것 같다.
ft_memmove.c
void *ft_memmove(void *dst, const void *src, size_t len)
{
size_t i;
size_t j;
unsigned char *addr;
j = 0;
addr = dst;
if (dst == 0 && src == 0)
return (0);
if (dst > src)
{
i = len - 1;
while (j < len)
{
addr[i] = *((unsigned char *)(src + i));
i--;
j++;
}
return (dst);
}
else
return (ft_memcpy(dst, src, len));
}
strlcpy
문자열을 사이즈만 큼 복사해주는 함수이다. 같은 기능을 하는 strncpy를 보완한 함수이다. 문자열 마지막에 NULL을 붙인다. 복사하는 문자여의 길이를 리턴한다.
ft_strlcpy.c
size_t ft_strlcpy(char *dst, const char *src, size_t dstsize)
{
size_t src_len;
size_t i;
src_len = ft_strlen(src);
i = 0;
if (dstsize == 0)
return (src_len);
else
{
while (*(src + i) && (i + 1 < dstsize))
{
dst[i] = src[i];
i++;
}
dst[i] = '\0';
}
return (src_len);
}
strlcat
두 문자열을 붙이는 함수이다. 마찬가지로 문자열 마지막에는 NULL이 붙는다. 다만, 주어진 길이만큼까지만 문자열을 붙인다.
ft_strlcat.c
size_t ft_strlcat(char *dst, const char *src, size_t dstsize)
{
size_t dst_len;
size_t src_len;
size_t i;
i = 0;
dst_len = ft_strlen(dst);
src_len = ft_strlen(src);
if (dst_len >= dstsize)
return (src_len + dstsize);
else
{
while (src[i] && (dst_len + i + 1 < dstsize))
{
dst[dst_len + i] = src[i];
i++;
}
dst[dst_len + i] = '\0';
}
return (src_len + dst_len);
}
toupper
인자로 받은 알파벳을 대문자로 바꿔서 리턴하는 함수이다.
ft_toupper.c
int ft_toupper(int c)
{
if (97 <= c && c <= 122)
return (c - 32);
else
return (c);
}
tolower
인자로 받은 알파벳을 소문자로 바꿔서 리턴하는 함수이다.
ft_tolower.c
int ft_tolower(int c)
{
if (65 <= c && c <= 90)
return (c + 32);
else
return (c);
}
strchr
문자열 내에 일치하는 문자가 있는지 확인한다. 있다면 해당 문자 위치의 포인터를 반환, 없다면 NULL을 반환한다.
ft_strchr.c
char *ft_strchr(const char *s, int c)
{
c = (char)c;
if (c == 0)
return ((char *)(s + ft_strlen(s)));
while (*s)
{
if (*s == c)
return ((char *)s);
s++;
}
return (0);
}
strrchr
문자열의 뒤에서부터 일치하는 문자가 있는지 확인한다.
ft_strrchr.c
char *ft_strrchr(const char *s, int c)
{
const char *addr;
c = (char)c;
if (c == 0)
return ((char *)(s + ft_strlen(s)));
addr = s + ft_strlen(s) - 1;
while (*addr)
{
if (*addr == c)
return ((char *)addr);
if (addr == s)
break ;
addr--;
}
return (0);
}
strncmp
두 문자열을 특정 길이만큼 비교한다. 주어진 길이가 문자열보다 크다면 문자열의 길이까지만 비교한다.
ft_strncmp.c
int ft_strncmp(const char *s1, const char *s2, size_t n)
{
size_t i;
i = 0;
while (((unsigned char)*s1 || (unsigned char)*s2) && i < n)
{
if ((unsigned char)*s1 != (unsigned char)*s2)
return ((unsigned char)*s1 - (unsigned char)*s2);
s1++;
s2++;
i++;
}
return (0);
}
memchr
strchr과 비슷하지만, 문자열 만이 아닌 특정 메모리 안에서 특정 값을 검색하여 해당 주소를 리턴한다.
ft_memchr.c
void *ft_memchr(const void *s, int c, size_t n)
{
size_t i;
unsigned char chr;
unsigned char *s_copy;
i = 0;
chr = (unsigned char)c;
s_copy = (unsigned char *)s;
while (i < n)
{
if (s_copy[i] == chr)
return (s_copy + i);
i++;
}
return (0);
}
memcmp
특정 두 메모리의 값을 비교한다.
ft_memcmp.c
int ft_memcmp(const void *s1, const void *s2, size_t n)
{
size_t i;
i = 0;
while (i < n)
{
if (*(unsigned char *)(s1 + i) != *(unsigned char *)(s2 + i))
return (*(unsigned char *)(s1 + i) - *(unsigned char *)(s2 + i));
i++;
}
return (0);
}
strnstr
문자열 haystack에서 길이 len만큼 needle이 있는지 검사한다.
ft_strnstr.c
char *ft_strnstr(const char *haystack, const char *needle, size_t len)
{
size_t i;
size_t j;
i = 0;
if (!(*needle))
return ((char *)haystack);
while (i < len && *(haystack + i))
{
j = 0;
if (*(haystack + i) == *needle)
{
while (j + i < len && *(needle + j) && *(haystack + i + j))
{
if (*(needle + j) != *(haystack + i + j))
break ;
j++;
}
if (j == ft_strlen(needle))
return ((char *)(haystack + i));
}
i++;
}
return (0);
}
atoi
문자열을 정수로 변환한다.
ft_atoi.c
static int get_sign(char str, int *i)
{
if (str == '-' || str == '+')
{
(*i)++;
if (str == '-')
return (-1);
return (1);
}
if ('0' <= str && str <= '9')
return (1);
return (0);
}
int ft_atoi(const char *str)
{
int i;
int sign;
int num;
i = 0;
num = 0;
sign = 1;
while ((str[i] == ' ') || (9 <= str[i] && str[i] <= 13))
i++;
sign = get_sign(str[i], &i);
while ('0' <= str[i] && str[i] <= '9')
num = num * 10 + (str[i++] - 48);
return (sign * num);
}
아래 두 함수는 구현하기 위해 malloc함수가 허용된다. 메모리를 할당할 때는 항상 누수에 주의하자.
- calloc
- strdup
calloc
특정 사이즈만큼 메모리를 할당한다. malloc과 비슷하지만, 요소를 0으로 초기화한다는 특징이 있다.
ft_calloc.c
void *ft_calloc(size_t count, size_t size)
{
void *addr;
if (size * count > SIZE_MAX)
return (0);
addr = malloc(size * count);
if (!(addr))
return (0);
ft_bzero(addr, count * size);
return (addr);
}
strdup
새로 동적할당된 메모리에 문자열을 복사하여 리턴하는 함수이다.
ft_strdup.c
char *ft_strdup(const char *s1)
{
int s1_len;
int i;
char *return_arr;
s1_len = ft_strlen(s1);
i = -1;
return_arr = (char *)malloc(sizeof(char) * (s1_len + 1));
if (!return_arr)
return (0);
while (s1[++i])
return_arr[i] = s1[i];
return_arr[i] = '\0';
return (return_arr);
}
substr
주어진 문자열으로부터 start ~ start + len 까지 문자열을 추출(새로 할당)하는 함수이다.
ft_substr.c
char *ft_substr(char const *s, unsigned int start, size_t len)
{
char *str;
size_t i;
size_t to_substr_len;
size_t s_len;
s_len = ft_strlen(s);
i = 0;
to_substr_len = 0;
if (s_len < start)
to_substr_len = 0;
else if (start + len > s_len)
to_substr_len = s_len - start;
else
to_substr_len = len;
str = (char *)malloc(sizeof(char) * (to_substr_len + 1));
if (!str)
return (0);
while (i < to_substr_len && s[start + i])
{
str[i] = s[start + i];
i++;
}
str[i] = '\0';
return (str);
}
strtrim
문자열 앞 뒤의 공백을 제거하는 함수이다. 새로운 문자열로 반환한다.
ft_strtrim.c
static int get_end_addr(char const *s1, char const *set)
{
size_t len;
if (!(*s1))
return (0);
len = ft_strlen(s1);
if (len)
len--;
else
return (0);
while (*(s1 + len))
{
if (!ft_strchr(set, *(s1 + len)))
break ;
else
len--;
}
return (len + 1);
}
static int get_start_addr(char const *s1, char const *set)
{
size_t i;
i = 0;
if (!(*s1))
return (0);
while (*s1)
{
if (ft_strchr(set, *s1) == 0)
break ;
else
i++;
s1++;
}
return (i);
}
char *ft_strtrim(char const *s1, char const *set)
{
int start;
int end;
char *return_str;
int i;
start = get_start_addr(s1, set);
end = get_end_addr(s1, set);
if (start > end)
{
return_str = ft_strdup("");
return (return_str);
}
return_str = (char *)malloc(sizeof(char) * (end - start + 1));
if (!return_str)
return (0);
i = -1;
while (start < end)
{
return_str[++i] = s1[start];
start++;
}
return_str[i + 1] = '\0';
return (return_str);
}
split
대망의 split이다. 만약 libft에서 어찌저찌 넘어갈 생각이어도 통과한 이후에 꼭 제대로 만들어놓는 것을 추천한다. 앞으로 많이 쓸 거다.
주어진 문자열을 특정 문자를 기준으로 잘라서 반환하는 함수이다.
ft_split.c
static int get_word_len(char const *str, char c)
{
int str_index;
int count;
str_index = 0;
count = 0;
while (str[str_index] && str[str_index] == c)
str_index++;
while (str[str_index] && str[str_index] != c)
{
str_index++;
count++;
}
return (count);
}
static int get_word_count(char const *str, char c)
{
int str_index;
int word_count;
str_index = 0;
word_count = 0;
while (str[str_index])
{
while (str[str_index] && str[str_index] == c)
str_index++;
if (str[str_index])
word_count++;
while (str[str_index] && str[str_index] != c)
str_index++;
}
return (word_count);
}
static char *arr_input(char const *str, char c, int word_len)
{
int arr_index;
int str_index;
char *arr;
arr_index = 0;
str_index = 0;
arr = (char *)malloc(sizeof(char) * (word_len + 1));
if (!arr)
return (0);
arr[word_len] = 0;
while (str[str_index] && str[str_index] == c)
(str_index)++;
while (str[str_index] && str[str_index] != c)
{
arr[arr_index] = str[str_index];
arr_index++;
str_index++;
}
return (arr);
}
char **free_all(char **arr, int word_count)
{
int i;
i = -1;
while (++i < word_count)
{
if (!arr[i])
{
i = -1;
while (++i < word_count)
free(arr[i]);
free(arr);
return (0);
}
}
return (arr);
}
char **ft_split(char const *s, char c)
{
char **arr;
int word_count;
int word_len;
int i;
int str_index;
word_count = get_word_count(s, c);
arr = (char **)malloc(sizeof(char *) * (word_count + 1));
if (!arr)
return (0);
i = -1;
str_index = 0;
while (++i < word_count)
{
word_len = get_word_len(&s[str_index], c);
arr[i] = arr_input(&s[str_index], c, word_len);
while (s[str_index] == c)
str_index++;
str_index += word_len + 1;
}
arr[i] = 0;
arr = free_all(arr, word_count);
return (arr);
}
itoa
정수를 문자열로 바꿔서 변환하는 함수이다.
ft_itoa.c
static int get_digit_count(long long n)
{
int count;
long long num;
count = 1;
num = n;
if (n < 0)
{
count++;
num = -n;
}
while (num / 10 > 0)
{
num /= 10;
count++;
}
return (count);
}
char *ft_itoa(int n)
{
long long num;
char *str;
int digit;
digit = get_digit_count((long long)n);
str = (char *)ft_calloc(digit + 1, sizeof(char));
if (!str)
return (0);
if (n < 0)
{
num = -((long long)n);
str[0] = '-';
}
else
num = (long long)n;
str[digit] = '\0';
while (--digit >= 0)
{
if (str[digit] == '-')
break ;
str[digit] = num % 10 + '0';
num /= 10;
}
return (str);
}
strmapi
주어진 문자열을 순회하며 각각 주어진 함수를 적용시켜 반환하는 함수이다.
ft_strmapi.c
char *ft_strmapi(char const *s, char (*f)(unsigned int, char))
{
char *str;
size_t len;
size_t i;
if (!s || !f)
return (0);
len = ft_strlen(s);
str = (char *)malloc(sizeof(char) * (len + 1));
if (!str)
return (0);
str[len] = '\0';
i = 0;
while (i < len)
{
str[i] = f(i, s[i]);
i++;
}
return (str);
}
striteri
주어진 문자열을 순회하며 각각 함수만 적용시킨다.
ft_striteri.c
void ft_striteri(char *s, void (*f)(unsigned int, char*))
{
size_t len;
size_t i;
len = ft_strlen(s);
i = 0;
while (i < len)
{
f(i, &s[i]);
i++;
}
}
ft_putchar_fd
파일 디스크립터를 받아 문자를 적는 함수이다.
ft_putchar_fd.c
void ft_putchar_fd(char c, int fd)
{
write(fd, &c, 1);
}
ft_putstr_fd
파일 디스크립터를 받아 문자열을 적는 함수이다.
ft_putstr_fd.c
void ft_putstr_fd(char *s, int fd)
{
size_t i;
size_t len;
i = 0;
len = ft_strlen(s);
while (i < len)
{
write(fd, &s[i], 1);
i++;
}
}
지금 생각해보면 이 것도 ft_putchar_fd 를 재탕하면 됐는데 말이다.
ft_putendl_fd
파일 디스크립터를 받아 주어진 문자열을 개행과 함께 적는 함수이다.
ft_putendl_fd.c
void ft_putendl_fd(char *s, int fd)
{
ft_putstr_fd(s, fd);
write(fd, "\n", 1);
}
ft_putnbr_fd
파일 디스크립터를 받아 숫자를 적는 함수이다.
ft_putnbr_fd.c
void ft_putnbr_fd(int n, int fd)
{
if (n >= 0)
{
if (n >= 10)
{
ft_putnbr_fd(n / 10, fd);
ft_putnbr_fd(n % 10, fd);
}
else
ft_putchar_fd(n + '0', fd);
}
else
{
if (n == -2147483648)
write(fd, "-2147483648", 11);
else
{
ft_putchar_fd('-', fd);
ft_putnbr_fd(-1 * n, fd);
}
}
}