[Libft] Part 1

이대현·2020년 4월 11일
3

42SEOUL

목록 보기
2/27

libft 프로젝트의 part1 함수들을 구현하면서 메모했던 내용들을 정리해두었다. 이 라이브러리의 함수들은 꾸준히 업데이트 되고 있기 때문에 가장 최신의 코드는 여기 깃허브 저장소를 참고...

1. ft_memset

    #include "libft.h"
    
    void        *ft_memset(void *dest, int c, size_t n)
    {
        unsigned char   *new_dest;
        unsigned char   src;
        size_t  i;
    
        new_dest = dest;
        src = c;
        i = 0;
        while(i++ < n)
            *new_dest++ = src;
        return (dest);
    }

dest 위치부터, n개의 바이트 만큼, c 라는 값으로 초기화 하는 함수.

  • 이 때 초기화 할 값인 c는 인자로는 int로 전달되지만 함수 내부적으로는 unsigned char로 형변환 되어서 사용된다.

    메모리에 접근할 때에는 signed char형이 아닌 unsigned char형을 사용해야하는 이유

    unsigned char 는 모든 bit를 투명하게 볼 수 있는 특성을 제공합니다.
    즉, 다른 type 은 내부 비트의 일부를 값을 표현하기 위한 용도가 아닌 다른 용도(부호 비트)로 사용할 수 있으나 unsigned char 는 이것이 허락되지 않습니다.

    따라서, 임의의 메모리에 바이트 단위로 접근해 값을 다룰 때에는 반드시 unsigned char 를 사용해야 full portability 를 얻을 수 있는 것입니다. 또한, 그와 같은 이유로 signed char 가 표현할 수 있는 값의 개수보다 unsigned char 가 표현할 수 있는 값의 개수가 많다는 사실에도 유의할 필요가 있습니다. signed char <-> unsigned char 사이의 값 변환이 1:1 로 이루어질 수 "없는" 경우도 있음을 의미합니다.

    이런 이유로, 표준이 바이트 값에 접근해야 하는 경우나 문자에 접근해야 하는 경우 (예: mem(), str() 함수들) 에는 모두 unsigned char 로 접근하도록 요구하고 있습니다.

    출처 : https://kldp.org/node/75686

  • 그리고, 바이트 단위로 초기화 하기 때문에 int형 배열을 초기화 할 때는 주의해야 한다.

    예를 들면 ft_memset(arr, 1, sizeof(arr))을 호출하면 arr 배열이 모두 1로 초기화 된다고 생각하는데, 실제로는 배열이 모두 16843009로 초기화 된다. 16843009의 2진수 표현은 0001 00000001 00000001 00000001 이다. 즉, arr 배열은 1바이트(8비트)당 1로 초기화 된 것이다. 바이트가 딱 떨어지게 초기화 되지 않기 때문에 int형 배열의 요소를 1로 초기화 할 수 없다. 다른 정수도 모두 마찬가지다. 딱 4개의 값만 가능하다고 한다. 0, -1, 0x3f, 0x7f.

    출처 : https://blog.naver.com/chogahui05/221484049429

  • size_t 자료형은 '이론상 가장 큰 사이즈를 담을 수 있는 unsigned 데이터 타입'으로 정의된다.

2. ft_bzero

    #include "libft.h"
    
    void    ft_bzero(void *b, size_t n)
    {
        unsigned char   *dest;
        size_t          i;
    
        dest = b;
        i = 0;
        while(i++ < n)
            *dest++ = 0;
        return (b);
    }

b 위치부터, n개의 바이트 만큼 0으로 초기화 하는 함수.

  • 이 함수는 더 이상 사용되지 않는다. memset을 사용하자.

3. ft_memcpy

    #include "libft.h"
    
    void                *ft_memcpy(void *dest, const void *src, size_t n)
    {
        unsigned char   *new_dest;
        unsigned char   *new_src;
        size_t          i;
        
        new_dest = dest;
        new_src = (unsigned char *)src;
        i = 0;
        while (i++ < n)
            *new_dest++ = *new_src++;
        return (dest);
    }
  • src 가 가리키는 곳 부터 n 바이트 만큼을 dest 이 가리키는 곳에 복사한다.
  • 이 함수는 source 의 널 종료 문자(null terminating character) 을 검사하지 않는다. 언제나 정확히 num 바이트 만큼을 복사한다.
  • 만일 두 메모리 블록이 겹쳐져 있다면 memmove 함수를 이용해야 한다. src의 원본 값이 이전 src로 바뀐 상태에서 복사를 해버리기 때문이다.
  • const void* src를 함수 내에서 unsigned char *로 강제 형변환을 하려고 했더니 경고가 떴다. const char 타입으로 받은 인수를 그냥 char 타입으로 다른 변수에 넘겨주려할 때 발생하는 경고라고 한다. 즉 const라는 qualifier(수식어,한정어)가 떨어져 나갔다는 뜻. const unsigned char *로 형변환 해줘야 함.

4. ft_memccpy

    #include "libft.h"
    
    void                  *ft_memccpy(void *dest, const void *src, int c, size_t n)
    {
        unsigned char     *new_dest;
        unsigned char     *new_src;
        unsigned char     find;
        size_t            i;
        
        new_dest = dest;
        new_src = (unsigned char *)src;
        find = c;
        i = 0;
        while(i < n)
        {
            new_dest[i] = new_src[i];
            if(new_src[i] == find)
                return (dest + (i + 1));
            i++;
        }
        return (0);
    }
  • n바이트의 데이터를 dest에 복제할 때에 src 데이터에서 문자 c를 만나면 c까지 복제하고 복제를 중단한다.

  • 복제된 dest변수에서 복제가 끝난 다음 번지를 return 한다. 만약 문자 c를 만나지 않았다면, n바이트를 복제하고 NULL을 return 한다.

  • 의문점

    강제 형변환에 대해서.

    형변환을 명시적으로 써주지 않으면 워닝이 뜬다? → const 변수에 대해서만 그런걸로. gcc -Wall 플래그를 설정하면 에러로 처리해 컴파일이 되지 않는다.

5. ft_memmove

    #include "libft.h"
    
    void                    *ft_memmove(void *dest, const void *src, size_t n)
    {
        unsigned char       *new_dest;
        unsigned char *new_src;
    
        if (dest == src || n == 0)
    		return (dest);
        if (dest < src)
        {
            new_dest = (unsigned char *)dest;
            new_src = (unsigned char *)src;
            while (n--)
                *new_dest++ = *new_src;
        }
        else
        {
            new_dest = dest + (n - 1);
            new_src = src + (n - 1);
            while (n--)
                *new_dest-- = *new_src--;
        }
        return (dest);
    }

memmove 사용법 및 구현 - C 메모리 이동

src메모리 영역에서 dest 메모리 영역으로 n 바이트 만큼 복사한다. 이 때, src 배열은 src와 dest의 메모리 영역과 겹치지 않는 메모리 영역부터 먼저 복사한다.

  • 메모리 영역이 overlap 되는 경우

    사실 잘 상상이 안됐었는데...

    1. 한 배열 안에서 복사를 수행할 때

    2. 그러면서 src 시작 주소가 dest 시작 주소보다 앞에 있을 때

      오버랩의 가능성이 생긴다.

  • overlap 해결 방안은?

    src의 마지막 주소 -> dest의 마지막 주소, 즉 뒤에서 부터 한 바이트 씩 복사한다.

6. ft_memchr

    #include "libft.h"
    
    void    *ft_memchr(const void *b, int c, size_t n)
    {
        unsigned char *new_b;
        unsigned char find;
    
        new_b = (unsigned char *)b;
        find = c;
        while (n--)
        {
            if (*new_b++ == find)
                return ((void *)(new_b - 1));
        }
        return (0);
    }

메모리 블록에서의 문자를 찾는다.

ptr 이 가리키는 메모리의 처음 num 바이트 중에서 처음으로 value 와 일치하는 값의 주소를 리턴한다.

  • 메모리 블록에서 value 와 일치하는 값이 있다면 그 곳의 주소를 리턴하고 값을 찾지 못한다면 NULL 을 리턴한다.

  • 의문점 1

      warning: return discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
                   return ((new_b - 1));

    컴파일 시 이런 경고가 떠서 return 값을

      return ((void *)(new_b - 1));

    이렇게 바꿨더니 해결됐다.

    혹은 아래 처럼 선언부에서 const 한정자를 사용하지 않아도 해결된다.

      unsigned char* new_b = (unsigned char *)b;

    참고 : https://igotit.tistory.com/entry/char-const-char-char-const

  • 의문점 2

    반환형이 void 면 반환할 때 다시 void 로 형변환을 한 뒤에 반환해야 하나? malloc에서는 안그랬던 것 같은데...

    https://pang2h.tistory.com/253

  • 인덱스로 표현하는 게 가독성이 더 좋을 것 같아서 수정함.

      void                *ft_memchr(const void *b, int c, size_t n)
      {
          size_t          i;
          unsigned char   *new_b;
          unsigned char   find;
      
          new_b = (unsigned char *)b;
          find = c;
          i = 0;
          while (i < n)
          {
              if (new_b[i] == find)
                  return ((new_b + i));
              i++;
          }
          return (0);
      }

7. ft_memcmp

    #include "libft.h"
    
    int     ft_memcmp(const void *b1, const void *b2, size_t n)
    {
        unsigned char *s1;
        unsigned char *s2;
        size_t i;
    
        if ((b1 == 0 && b2 == 0) || n == 0)
            return (0);
        else if (s1 == 0 || s2 == 0)
    		return (s1 == 0 ? -1 : 1);
        s1 = (unsigned char *)b1;
        s2 = (unsigned char *)b2;
        i = 0;
        while (n--)
        {
            if (s1[i] != s2[i])
                break ; 
            i++;
        }
        return (s1[i] - s2[i]);
    }

ptr1 이 가리키는 처음 num 바이트의 데이터와 ptr2 가 가리키는 처음 num 바이트의 데이터를 비교한다.

  • 두 개의 메모리 블록의 관계에 따라 아래와 같이 정수 값을 리턴한다.

    • 만일 두 메모리 블록이 정확히 같다면 0 을 리턴한다.
    • 만일 두 메모리 블록이 다를 경우, ptr1ptr2 가 가리키는 메모리 블록에서 앞에서 부터 처음으로 다른 바이트를 살펴 보는데, 그 바이트를 unsigned char 로 해석하였을 때, 그 값이 ptr1 이 더 크면 0 보다 큰 값을, 아니면 0 보다 작은 값을 리턴한다.
  • 오버플로우는 처리를 못해주는 것 같다. 같은 문자열인데도 n값을 크게 주면 0이 아니라 쓰레기값끼리 비교한 값을 리턴한다.

    -> 항상 n은 버퍼 사이즈보다 같거나 작게.

  • strncmp와의 차이점?

    • 두 문자열 중 하나가 끝나더라도(널 값이 나오더라도) 상관없이 서로 다른 값이 나오거나, n개가 될 때 까지 비교를 수행한다.
    • strncmp는 s1과 s2가 모두 NULL값이 나오면 남은 카운트에 관계없이 0을 반환했던 것과 차이가 있다.
  • strlen

  • strlcpy

  • strlcat

8. ft_strchr

    #include "libft.h"
    
    char    *ft_strchr(const char *s, int c)
    {
        char find;
    
        find = c;
        while (*s++ != '\0')
        {
            if (*s == find)
                return ((char *)s);
        }
        return (0);
    }

문자열에서 특정 문자를 찾을 때 사용하는 함수.

  • 문자열 s에서 첫 번째로 찾은 문자 c의 포인터를 리턴한다.
  • 만일 찾는 문자가 없다면 NULL포인터를 리턴한다.
  • 이 때 마지막 NULL 문자도 C 문자열의 일부로 간주하기 때문에 이 함수는 문자열의 맨 끝 부분을 가리키는 포인터를 얻기 위해 사용할 수 도 있다.
  • int c는 검색할 문자로, int 형태로 형변환 되어서 전달되지만 함수 내부적으로는 다시 char 형태로 처리된다.

9. ft_strrchr

    #include "libft.h"
    
    char            *ft_strrchr(const char *s, int c)
    {
        char        *last;
        char        find;
        size_t      i;
        
        last = (char *)s;
        find = (char)c;
        i = ft_strlen(s);
        while (i > 0)
        {
            if (last[i] == find)
                return (last + i);
            i--;
        }
        if (last[i] == find)
            return (last);
        return (0);
    }

문자열 s에서 마지막으로 있는 문자 c의 포인터를 리턴한다.

  • 예외처리

      if (last[i] == find)        
      		return (last);

    s가 빈문자열일 때, s의 첫 글자만 c일 때 NULL이 아니라 s의 첫글자를 반환해야 함.

10. ft_strnstr

    #include "libft.h"
    
    char        *ft_strnstr(const char *big, const char *little, size_t len)
    {
        size_t  l_len;
        size_t  b_len;
        size_t  size;
    
        if (*little == '\0')
            return ((char *)big);
        l_len = ft_strlen(little);
        b_len = ft_strlen(big);
        if (b_len < l_len || len < l_len)
    		return (0);
    	size = b_len > len ? len : b_len;
        while (size-- >= l_len)
        {
            if (ft_memcmp(big, little, l_len) == 0)
                return ((char *)big);
            big++;
        }
        return (0);
    }

문자열을 검색한다.

  • len 이하의 big 에서 little 를 검색하여 가장 먼저 나타나는 곳의 위치를 리턴한다.
  • 이 때 일치하는 문자열이 없다면 널 포인터를 리턴하게 된다.
  • 검색에서 마지막 널 문자는 포함하지 않는다.
  • core dumped 에러
    • printf("%s\n", NULL) 이라고 했을 경우에 gcc컴파일러는 segfault를 띄우더라고요..구글링 좀 해보니까 gcc컴파일러가 그렇다고 하더라고요. -slack donglee 님
    • 저도 계속 seg fault가 떠서 테스트를 해볼 때 printf 내부에서 %s 앞에 ft : 라던지 뭐든 구분할 수 있는 문자를 넣어줍니다!! -slack joockim님
    • 참고 : https://guimong.tistory.com/entry/printf%EC%9D%98-Segmentationg-fault-%EC%B0%A8%EC%9D%B4
  • 남은 size의 길이가 l_len 길이 보다 작다면, 비교할 의미가 없으므로 NULL을 리턴

11. ft_strncmp

    #include "libft.h"
    
    int                 ft_strncmp(const char *s1, const char *s2, size_t n)
    {
        size_t          i;
        unsigned char   *b1;
        unsigned char   *b2;
    
        b1 = (unsigned char *)s1;
        b2 = (unsigned char *)s2;
        if (b1 == 0 && b2 == 0)
    		return (0);
    	else if (b1 == 0 || b2 == 0)
    		return (b1 == 0 ? -1 : 1);
        if (n == 0)
            return (0);
        i = 0;
        while ((b1[i] != '\0') && (b2[i] != '\0') && i < (n - 1))
        {
            if (b1[i] != b2[i])
                break ;
            i++;
        }
        return (b1[i] - b2[i]);
    }
  • 의문점

    return 할 때 unsigned char로 형변환 하는 코드를 봤다. 필요한가?

    • 2020.03.10 해결

      ft_strncmp("test\200", "test\0", 6) 같은

      128 이상의 아스키코드 수도 계산하기 위해서 unsigned char *로의 형변환이 필요했다.

12. ft_atoi

    #include "libft.h"
    
    int     ft_isspace(char c)
    {
        if (c == ' ' || c == '\n' || c == '\t' ||
    			c == '\v' || c == '\f' || c == '\r')
            return (1);
        else
            return (0);
    }
    
    int     ft_atoi(const char *str)
    {
        int nbr;
        int sign;
        int i;
    
        nbr = 0;
        sign = 1;
        i = 0;
        while ((str[i] != '\0') && ft_isspace(str[i]) == 1)
            i++;
        if (str[i] == '-')
            sign = -1;
        if ((str[i] == '-') || (str[i] == '+'))
            i++;
        while ((str[i] != '\0') && ('0' <= str[i]) && (str[i] <= '9'))
        {
            nbr = (nbr * 10) + (str[i] - '0');
            i++;
        }
        return (sign * nbr);
    }

13. ft_isalpha

    #include "libft.h"
    
    int     ft_isalpha(int c)
    {
        return (((65 <= c) && (90 >= c)) || ((97 <= c) && (122 >= c)));
    }

연산자도 참(1)과 거짓(0)을 반환한다.

  • isdigit
  • isalnum
  • isascii
  • isprint

14. ft_toupper

    #include "libft.h"
    
    int     ft_islower(int c)
    {
        return((97 <= c) && (122 >= c));
    }
    
    int	    ft_toupper(int c)
    {
    	if (ft_islower(c))
    		return (c - 32);
    	return (c);
    }
  • 인자가 소문자일 경우 대문자로 바꿔 반환한다.

  • 소문자가 아닌 인자가 들어왔을 때는 그대로 반환한다.

  • tolower

15. ft_calloc

    #include "libft.h"
    
    void    ft_bzero(void *b, size_t n)
    {
        unsigned char   *dest;
        size_t          i;
    
        dest = b;
        i = 0;
        while(i++ < n)
            *dest++ = 0;
    }
    
    void    *ft_calloc(size_t nmemb, size_t size)
    {
        void *mem;
    
        if (!(mem = malloc(nmemb * size)))
            return (NULL);
        ft_bzero(mem, (nmemb * size));
        return(mem);
    }
  • size 크기의 변수를 nmemb개 만큼 저장할 수 있는 메모리 공간을 할당한다.
  • 그리고 동적으로 할당한 메모리의 모든 비트를 0으로 설정한다.
  • malloc 과의 차이점은?

16. ft_strdup

    #include "libft.h"
    
    char    *ft_strdup(const char *str)
    {
        int i;
        int len;
        char *new_str;
    
        len = 0;
        while (str[len])
            len++;
        new_str = (char *)malloc(sizeof(char) * (len + 1));
        if (!(new_str))
            return (NULL);
        i = 0;
        while (str[i])
        {
            new_str[i] = str[i];
            i++;
        }
        new_str[i] = '\0';
        return (new_str);
    }
  • 문자열 str 길이 + 1 크기를 malloc으로 할당 후 문자열 str을 복사한 후 반환한다.
  • 반환받은 메모리는 반드시 free를 통하여 메모리를 해제해야 한다.
  • malloc + strcpy
profile
삽질의 기록들 👨‍💻

0개의 댓글