Libft

1. 새롭게 알게 된 점

  • 함수 파라미터의 const
    • const char *cache - 포인터 내부의 값을 못 바꿈
    • char const *cache - 포인터 내부의 값을 못 바꿈
    • char * const cache - 포인터의 주소값을 못 바꿈
    • char const * const cache - 가르키는 값, 내부 값 둘다 못 바꿈
    • const char * const cache - 가르키는 값, 내부 값 둘다 못 바꿈

    Const란? 컴파일러 상수(*상수 : 정적인 data. 초기화를 한번밖에 못하는 data. 메모리 변경이 불가하다).
    변수를 상수화하여, 한번 할당된 상수로 할당된 메모리의 모든 비트를 변경하지 못한다.
    Const의 장점? const키워드가 붙은 객체는 외부 변경을 불가능하게 한다.
    class 바깥에서는 전역 / namespace 유효범위의 상수를 정의하는데 씀.
    static 선언한 객체에도 const를 붙이는 것이 가능하다.정적 / 비정적 data 멤버 모두를 상수로 선언할 수 있다.

  • size_t

    size_t는 어떤 타입의 사이즈 이던지 간에 충분한 bytes를 가진 unsigned long이다.
    이론상으로 가장 큰 사이즈를 담을 수 있는 unsigned 데이터 타입
    size_t는 32bit 운영체제에서 부호 없는 32비트 정수이고, 64bit 운영체제에선부호 없는 64bit 정수이다 (가장 큰 크기를 보장 받을 수 있다.)

    하지만, unsigned int 또는 int는 64bit OS라고 해서 꼭64bit 정수가 아닐 수 있다. 여전히 32bit일 수도 있다. 이게 size_t와 unsigned int형의 차이이다.

  • 컴파일을 cc로 하는 이유

    CC(C Compiler)는 말 그대로 C언어를 기계어로 바꿔주는 컴파일러를 의미한다.
    즉 C언어 컴파일러 모두를 CC라고 하는 것이다.
    특히 LINUX 상에서는 cc와 gcc 가 심볼릭 링크로 연결되어 있기 때문에 LINUX 상에서는 CC가 GCC가 된다.

  • static

    static 변수는 프로그램이 시작될때 할당되고, 프로그램이 끝날때 회수된다.
    DS에 할당됨으로써, 전역으로 할당된 변수는 해당 소스파일 내의 모든 함수에서 사용이 가능하고,
    이러한 점을 이용하여 다른 소스파일에서는 사용하지 못하기 때문에 정보의 은닉이 가능하다.
    내부정적변수 - 다른함수, 외부정적변수 - 다른 소스파일 에서 접근 불가

  • NULL

    NULL은 stdio, stdlib등의 헤더파일에 정의되어있음
    (void *)0을 가르킨다. 메모리 주소 0을 가르키고 있음
    일반적으로 접근할 수 없는 메모리영역이다 따라서 포인터가 아무것도 안가르킬때 정의한다.

  • NULL GUARD

    동적 할당 할 수 있는 메모리가 있다면 포인터 변수가 할당 받은 메모리의 첫번째 위치를 가리키게 될 것이고
    동적 할당 할 수 있는 메모리가 없다면 NULL 포인터를 가리키게 될 것이다.
    즉, NULL 포인터를 받게된 것을 체크하지 않고 그대로 프로그램을 진행하면 보호되어 있는 0번지 주소를 참조하려고하면서 에러가 나게 된다.
    NULL 포인터 역참조가 일어나면서 에러가 나게 된다. 해결 방법 : NULL 체크

2.Makefile

  • .PHONY

    1. phony를 쓰는 이유중 하나는 실제 파일명과 타겟이름간의 충돌을 피해준다.
    2. make file의 리링크가 일어날 경우
      하위 make에서 에러가 발생해도 계속 빌드가 진행된다.
      하나의 규칙만이 실행되기에 make의 장점인 병렬 수행이 되지 않는다.
      라는 단점들을 해결 할 수 있다.

3. 각 함수에 대한 설명

  • ft_atoi

    오버, 언더플로우가 나는 경우 -> man에서는 atoi의 오류처리는 따로 하지 않는다고 명시.
    내부 atoi 함수는 strtol을 돌려 나오는 오류 리턴값을 사용하지만, atoi에서는 오버플로우 -1, 언더플로우 0을 반환한다.

  • ft_strchr

    인덱싱을 할 때 int를 사용한 인덱싱을 하면 안되고, size_t를 사용한 인덱싱을 해야하는 이유

      #include "libft.h"
        
      char	*ft_strchr(const char *s, int c)
      {
      	char	save;
      	int		index;
        
      	save = (unsigned char)c;
      	index = 0;
      	while (s[index] != '\0')
      	{
      		if (s[index] == save)
      			return ((char *)s + index);
      		index++;
      	}
      	if (s[index] == save)
      		return ((char *)s + index);
      	return ((char *)0);
      }
      // int index = 0 을 이용한 인덱싱
    

    해당 코드를 사용하여, 반례 케이스를 찾을 수 있는 메인문

      #include <stdio.h>
      #include <string.h>
      #include "libft.h"
        
      int	main(void)
      {
      	char	*cache;
        
      	cache = (char *)malloc(sizeof(char) * (2147483657));
      	memset(cache, 1, 2147483657);
      	cache[2147483656] = 'X';
      	printf("%s	\n", strchr(cache, 'X'));
      	printf("%s	\n", ft_strchr(cache, 'X'));
        
      	return (0);
      }
    

    Untitled.png

    해당 main 소스코드를 실행한 결과
    int범위를 넘어가게 힙 동적할당을 한 후, 끝부분의 값을 strchr하려는 경우 size_t를 사용한, string.h의 strchr은 문제없이 X의 주소값을 찾아서 return 해주지만, int를 사용하여 X의 주소값을 찾는 ft_strchr의 경우 주소값을 return 하지 못하고 null이 출력되었다.

    따라서, 인덱싱을 할 때는 size_t를 사용하거나, 포인터에 ++을 붙히는 방식이 적절 할 것 같다.

    하지만, 위와같은 방법을 사용하여도 프로그래머가 의도적으로 size_t 그 이상의 수를 (int 그 이상의 수를 input 한것 처럼) input 하면 당연히, 위와같은 오류가 나기 때문에 소스코드 사용자가 올바른 방법으로 사용하는 것이 중요하다.

  • ft_memset

    ft_memset에서의 프로그래머의 실수가 일어날 수 있는 부분

      #include <stdio.h>
      #include "libft.h"
        
      int	main(void)
      {
      	int	*cache;
        
      	cache = (int *)malloc(sizeof(int) * (5));
      	ft_memset(cache, 1, 5);
      	printf("%d	\n", cache[0]);
        
      	return (0);
      }
    

    Untitled%201.png

    해당 main 함수를 실행하면 정수 1이 출력될 줄 알았지만, 16843009가 출력되었다.
    memset의 소스코드를 보면 다음과 같다.

          #include "libft.h"
            
          void	*ft_memset(void *b, int c, size_t len)
          {
              unsigned char	*dest;
              unsigned char	cache;
            
              dest = b;
              cache = (unsigned char)c;
              while (len--)
                  *dest++ = cache;
              return (b);
          }
    

    memset 함수에서 가장 작은 자료형인 unsigned char(음수인 메모리 주소는 없으니까) 를사용하여 메모리공간을 초기화함을 알 수 있다.
    이때 1byte씩 즉, 00000001 00000001 … 이렇게 메모리가 초기화가 되는데,main 함수에서 정수 자료형인 int 즉 4byte로 출력하게 구현하였으니, 16843009가 출력된다.

    Untitled%202.png

Leave a comment