C Libft [Libft] Part 1 libft 프로젝트의 part1 함수들을 구현하면서 메모했던 내용들을 정리해두었다. 이 라이브러리의 함수들은 꾸준히 업데이트 되고 있기 때문에 가장 최신의 코드는 여기 깃허브 저장소를 참고...
libft 프로젝트의 part1 함수들을 구현하면서 메모했던 내용들을 정리해두었다. 이 라이브러리의 함수들은 꾸준히 업데이트 되고 있기 때문에 가장 최신의 코드는 여기 깃허브 저장소를 참고...
1. ft_memset
Copy #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
Copy #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
Copy #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
Copy #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
Copy #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 되는 경우
사실 잘 상상이 안됐었는데...
그러면서 src 시작 주소가 dest 시작 주소보다 앞에 있을 때
오버랩의 가능성이 생긴다.
overlap 해결 방안은?
src의 마지막 주소 -> dest의 마지막 주소, 즉 뒤에서 부터 한 바이트 씩 복사한다.
6. ft_memchr
Copy #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
Copy warning: return discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
return ((new_b - 1));
컴파일 시 이런 경고가 떠서 return 값을
Copy return ((void *)(new_b - 1));
이렇게 바꿨더니 해결됐다.
혹은 아래 처럼 선언부에서 const 한정자를 사용하지 않아도 해결된다.
Copy unsigned char* new_b = (unsigned char *)b;
참고 : https://igotit.tistory.com/entry/char-const-char-char-const
인덱스로 표현하는 게 가독성이 더 좋을 것 같아서 수정함.
Copy 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
Copy #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
바이트의 데이터를 비교한다.
8. ft_strchr
Copy #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
Copy #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의 포인터를 리턴한다.
예외처리
Copy if (last[i] == find)
return (last);
s가 빈문자열일 때, s의 첫 글자만 c일 때 NULL이 아니라 s의 첫글자를 반환해야 함.
10. ft_strnstr
Copy #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님
남은 size의 길이가 l_len 길이 보다 작다면, 비교할 의미가 없으므로 NULL을 리턴
11. ft_strncmp
Copy #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
Copy #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
Copy #include "libft.h"
int ft_isalpha ( int c)
{
return ((( 65 <= c) && ( 90 >= c)) || (( 97 <= c) && ( 122 >= c)));
}
연산자도 참(1)과 거짓(0)을 반환한다.
14. ft_toupper
Copy #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);
}
15. ft_calloc
Copy #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으로 설정한다.
16. ft_strdup
Copy #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를 통하여 메모리를 해제해야 한다.