김현우
  • [42경산/C언어] Libft 라이브러리 구현 가이드: 표준 C 라이브러리 함수 재구현과 Makefile 설계
    2025년 03월 12일 23시 45분 12초에 업로드 된 글입니다.
    작성자: kugorang

    TL;DR — 3줄 핵심 요약

    • 문제: Libft는 표준 C 라이브러리 함수 40여 개를 직접 다시 만드는 과제다. "동작한다"와 "표준과 같다"는 다르고, 평가는 경계값에서 갈린다.

    • 기술/원인: C 문자열은 '\0'로 끝나는 바이트 배열이고, 포인터는 그 배열의 주소다. 함수 대부분은 이 주소를 진행시키며 바이트를 읽고, 종료 문자를 만나면 멈춘다. 그래서 버퍼 크기, 널 종료, 겹침 여부가 결과를 바꾼다.

    • 해결/인사이트: 세 가지만 지키면 대부분 통과한다. ① 반환 타입과 값(size_t, char*, int)을 표준과 맞춘다 ② 크기가 0인 경우와 널 종료를 따로 처리한다 ③ memcpy와 memmove, strlcpy와 strncpy의 차이를 구분한다.

    Part 1: libc 함수 다시 만들기

    문자 판별과 변환

    • ft_isalpha, ft_isdigit, ft_isalnum: 문자가 속한 범위를 비교해 0 또는 1을 반환한다. 표준은 "0이 아니면 참"이라 값이 1일 필요는 없다.
    • ft_isascii: 0~127 범위인지 확인한다. C 표준이 아니라 POSIX/BSD 확장이다.
    • ft_isprint: 공백(32)부터 물결표(126)까지.
    • ft_toupper, ft_tolower: 소문자 a(97)와 대문자 A(65)의 차이가 32라는 성질을 쓴다.
    int ft_isalpha(int c)
    {
        return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
    }
    
    int ft_tolower(int c)
    {
        if (c >= 'A' && c <= 'Z')
            return (c + 32);
        return (c);
    }

    char가 부호 있는 타입인 플랫폼에서는 음수 문자가 들어올 수 있다. 표준 함수는 unsigned char 범위를 가정하므로, 필요하면 (unsigned char)로 캐스팅한다.

    길이와 메모리 채우기

    size_t  ft_strlen(const char *s)
    {
        size_t  i = 0;
        while (s[i])
            i++;
        return (i);
    }
    
    void    *ft_memset(void *b, int c, size_t len)
    {
        unsigned char   *p = (unsigned char *)b;
        size_t          i = 0;
        while (i < len)
            p[i++] = (unsigned char)c;
        return (b);
    }
    • ft_strlen의 반환은 int가 아니라 size_t다. int면 아주 긴 문자열에서 넘친다.
    • ft_bzero(s, n)은 memset(s, 0, n)과 같다. 4.2BSD에서 온 함수로 POSIX.1-2008에서 제거되었다.
    • len이 0이면 아무것도 쓰지 않고 포인터를 그대로 반환한다.

    복사: memcpy와 memmove

    void    *ft_memmove(void *dst, const void *src, size_t len)
    {
        unsigned char   *d = (unsigned char *)dst;
        const unsigned char *s = (const unsigned char *)src;
    
        if (d < s)
            while (len--)
                *d++ = *s++;
        else if (d > s)
        {
            while (len--)
                d[len] = s[len];
        }
        return (dst);
    }
    • memcpy: 영역이 겹치면 결과가 정의되지 않는다. 앞에서 뒤로만 복사한다.
    • memmove: 겹쳐도 안전하다. dst가 src보다 앞이면 순방향, 뒤면 역방향으로 복사한다.
    • 역방향 복사에서 size_t를 쓰면 len이 0일 때 0-1이 매우 큰 값이 되므로 while (len--) 형태로 처리한다.

    안전한 문자열 복사: strlcpy와 strlcat

    size_t  ft_strlcpy(char *dst, const char *src, size_t dstsize)
    {
        size_t  i = 0;
        while (src[i] && i + 1 < dstsize)
        {
            dst[i] = src[i];
            i++;
        }
        if (dstsize > 0)
            dst[i] = '\0';
        while (src[i])
            i++;
        return (ft_strlen(src));
    }
    • dstsize - 1글자까지 복사하고 항상 널을 붙인다(단, dstsize가 0이면 아무것도 쓰지 않고 src 길이만 반환).
    • 반환값은 src의 전체 길이다. 반환값이 dstsize 이상이면 잘렸다는 뜻이다.
    • strncpy와의 차이: strncpy는 소스가 길면 널을 보장하지 않고, 짧으면 남는 공간을 0으로 채운다.

    검색과 비교

    • ft_strchr: 첫 번째 등장 위치. '\0'을 찾으면 문자열 끝을 가리키는 포인터를 반환한다.
    • ft_strrchr: 마지막 등장 위치. 끝에서부터 찾거나, 순방향으로 훑으며 마지막 위치를 기억한다.
    • ft_strnstr: haystack의 앞 len 문자 안에서만 needle을 찾는다. BSD 확장이라 표준에 없다. needle이 빈 문자열이면 haystack을 반환한다.
    • ft_strncmp: n개까지 사전식 비교. 차이를 (unsigned char)s1[i] - (unsigned char)s2[i]로 반환한다.
    • ft_memchr, ft_memcmp: 널을 만나도 멈추지 않고 n바이트를 본다.

    변환과 할당

    int ft_atoi(const char *str)
    {
        long    n = 0;
        int     sign = 1;
    
        while (*str == ' ' || (*str >= 9 && *str <= 13))
            str++;
        if (*str == '-' || *str == '+')
            if (*str++ == '-')
                sign = -1;
        while (*str >= '0' && *str <= '9')
            n = n * 10 + (*str++ - '0');
        return ((int)(n * sign));
    }
    • ft_calloc(count, size): count * size를 계산하기 전에 0인지 확인하고, 곱셈이 넘치지 않는지 검사해야 한다. 넘치면 NULL을 반환한다.
    • ft_strdup: strlen + 1 바이트를 할당해 복사한다. 할당 실패 시 NULL.

    Part 2와 Part 3

    • ft_substr: 문자열의 start부터 len만큼 잘라 새로 할당한다. start가 길이보다 크면 빈 문자열.
    • ft_strjoin: 두 문자열을 이어 붙인 새 문자열. 크기는 len1 + len2 + 1.
    • ft_strtrim: 양 끝에서 set에 포함된 문자를 제거한 새 문자열.
    • ft_split: 구분자로 나눈 문자열 배열. 마지막에 NULL 포인터를 둔다.
    • ft_itoa: 정수를 문자열로. INT_MIN(-2147483648)은 -n이 넘치므로 long으로 처리한다.
    • ft_strmapi, ft_striteri: 인덱스와 함께 각 문자에 함수를 적용한다.
    • ft_putchar_fd, ft_putstr_fd, ft_putendl_fd, ft_putnbr_fd: write(fd, ...) 기반 출력. 개행 유무가 함수마다 다르다.
    • 보너스(연결 리스트): ft_lstnew, ft_lstadd_front, ft_lstsize, ft_lstlast, ft_lstadd_back, ft_lstdelone, ft_lstclear, ft_lstiter, ft_lstmap

    자주 틀리는 지점

    • ft_strlen, ft_strlcpy의 반환을 int로 선언
    • ft_memmove에서 len이 0일 때 역방향 루프가 폭주
    • ft_strlcat에서 dst가 널로 끝나 있지 않은 경우를 처리하지 않음
    • ft_split에서 할당 실패 시 이미 만든 배열을 해제하지 않음
    • ft_itoa에서 INT_MIN 처리 누락
    • ft_strnstr에서 len이 needle 길이보다 작은 경우 검사 누락
    • Makefile에서 .PHONY 누락, 보너스 규칙 누락

    자주 묻는 질문 (FAQ)

    Q1. libft 함수들을 그냥 표준 라이브러리로 호출하면 안 되나요?

    A. 과제 규칙상 허용된 함수만 쓸 수 있고, 목적이 동작 원리를 이해하는 것이다. 또 이후 과제에서 같은 함수를 쓰기 때문에 직접 만든 구현이 필요하다. man 페이지와 동작을 비교하며 만들면 표준과의 차이를 확인할 수 있다.

    Q2. ft_strlcpy와 ft_strncpy 중 무엇을 써야 하나요?

    A. 크기를 아는 버퍼에 넣을 때는 strlcpy가 안전하다. 항상 널로 끝나고, 반환값으로 잘림을 알 수 있다. strncpy는 소스가 길면 널이 없을 수 있어 이어서 쓰면 위험하다.

    Q3. 보너스 연결 리스트는 꼭 해야 하나요?

    A. 평가 점수에 포함되지만 필수는 아니다. 다만 이후 과제(push_swap 등)에서 연결 리스트를 직접 다루므로, lstnew·lstadd_back·lstclear 정도는 반드시 익혀 두는 편이 좋다. lstclear는 노드와 content를 모두 해제해야 누수가 없다.

    728x90
    728x90
    댓글