libft 완성기(1) : is 시리즈 부터 str 시리즈까지

Ryu(Paul)·2021년 12월 17일
1

42_Seoul_생존기

목록 보기
5/7
post-thumbnail

시작하면서..

사실 이제 born2beroot 와 printf 과제를 시작하기 위해 준비하는 시점이다.. 꾸준히 한다고 했던 나 자신은 과연 어디 있는가...
우선, 과제 2개 정도를 돌파하고 나서 느끼는 바는 한 마디로 정리하면 이거다.

아 겁나게 어렵다..!

이유는 명확했다. 근본은 어디 가지 않는다. 힘이란 내공에서 튀어나오는 것이고, 순간 순간 재치로 넘어가는 것으로 창조의 영역에 가까운 코드를 복제 해낸다는 것은 결코 쉬운 일이 아닌 것이다.

포인터 ㅂㄷㅂㄷ...
구조체 ㅂㄷㅂㄷ....
연결리스트 ?????

그런 점에서 이번 달 더 많이 나간다고 나에게 엄청난 이득이 되는 상황은 아니기에, 짬을 내어 기본기였던 libft.h 작성을 복기해 보려고 한다. libft로 배울 수 있었던 핵심들 위주, 메모리 파트, 문자열 다루는 함수, 연결리스트 라던지, fd 함수 들. 되도록 디테일하게 정리해봐야지 싶다.
이 글을 통해 많은 C잼민이(?) 들에게 도움이 되었으면한다. 단, 개인적으로 내용 전체를 공유하는 건 좋지 않다고 생각된다. 역시 실력을 위해선 클론 코딩으론 한계가 명확하기 때문이다(애초에, 전체 공유는 안된다고 알고 있기도 하다...)

과제 목표와 개요

서브젝트를 통으로 공유하는 경우도 많던데, 이렇게는 안하려고 한다. 우선 해당 위치에 오면 번역 동아리도 있고 이것저것 자료는 널려 있으니 말이다. 그냥 핵심들만 어느정도 정리해보면..

  • 라이브러리를 만들어야 한다. 즉, recode 한 함수들, libft.h, 이를 컴파일 할 Makefile을 완성 시켜야 한다.
  • Norminette 은 필수 오브 필수
  • 컴파일 과정에서 relink는 되면 안되며, restrict 한정자는 프로토타입에 입력시키지 말 것.
  • 이해하고, 배워야 할 것은 대략 아래와 같다.
    • 문자열의 저장과 조작
    • 각 데이터 타입에 따른 오버플로우, 언더플로우 발생에 대한 개념
    • 메모리 구조에 대한 이해와 메모리 접근 원리 이해하기
    • 동적할당과 동적할당 취소에 대한 이해
    • 헤더 파일 작성에 대한 이해
    • fd 에 대한 이해
    • iteri의 개념과 mapi의 개념
    • 구조체 - 연결리스트 개념
    • Makefile (컴파일)의 개념 및 응용

내용을 보면서 곰곰히 고민해봤지만 생각보다 많다. 역시 라피신 이후에 여유있을거라 생각하지 말라던 학장님의 이야기는 괜한게 아니다 라고 생각이 든다(...)

Part 1 - Libc 함수들

C 표준 라이브러리라고 보면 된다. 가장 기본적인 데이터의 조작을 가능케 하거나, 찾는 것을 가능케 한다. 따라서 가장 근본이 되는 것이라고 보면 되고 아주 간단하고 직관적인 리턴값을 요구한다. 제일 쉽게 접근 하는 방법은 역시 man 을 활용하여서 내용을 읽고, 간단한 main함수를 통해 원본의 작동방식을 보는 것이다.

< is 시리즈 >

기본적으로 들어오는 int 값을 ascii 값으로 고려하여 해당하는 범위에 포함되는가? 를 확인한다.

  • isalpha : 알파벳이면 int 값을, 아닌 경우는 0을 반환한다.
  • isdigit : 숫자(0~9) 이면 int 값을, 아닌 경우는 0을 반환한다.
  • isalnum : 알파벳 혹은 숫자(0~9) 이면 int 값을, 아닌 경우 0을 반환한다.
  • isascii : ascii 코드로 지정된 값이면 int 값을, 아닌 경우 0을 반환한다.
  • isprint : 출력 가능한 문자의 경우 int 값을, 아닌 경우 0을 반환한다.
  • toupper : 들어온 값 중 알파벳 소문자는 대문자로 변경하여 반환한다.
  • tolower : 들어온 값 중 알파벳 대문자는 소문자로 변경하여 반환한다.

핵심은, 왜 int 값으로 패러미터를 받으며,
원하는 범위에 들어갈 때 어떤 값을 리턴하냐? 이다.

int 값을 받는 이유는?

C99 공식 문서 언급한 내용을 정리하면 아래와 같다. 파일을 읽거나 하여, 데이터를 함수로 보냈들 때 eof(end of file)의 시그널을 함께 보낸다. 그런데 이를 보통 -1 내지는 음수 값으로 지정한다. 따라서 이를 인지하는데 있어 다른 자료형보단 int로 하는 것이 명확하기 때문이다.
더 나아가서, 최초에 C 언어를 고려해볼 필요가 있다. prototype이란 기능 조차 없던 시절, 인수 검사를 진행할 때는 char 형도 존재 하지 않았고 데이터 입출력시 자동적으로 int형으로 형변환이 되어 전달되었었다. 그리고 하위호환을 계속 이어가는 상황이다 보니, 이제는 충분히 바꿀 수도 있겠지만, 현재의 상태로 굳어진 것이다. (물론 더 디테일한 내용은 있지만... 시간이 있다면 직접 찾아보길 바란다.) 참고원문링크

자잘 자잘하게 주요 포인트가 있다면 아래와 같다.

  1. print 라는 것은 출력 가능하다는 뜻. 그렇다면 int형으로 받을 때, 출력 가능한 값과 출력 불가능한 값중 어느 걸 기준으로 잡아야 할까?
  2. int 값을 반환하는 이유는 boolean 형으로 상세하게 지정된 것이 아닌 C에선 0을 제외한 모든 양수 값은 True 처리가 되기 때문이다.
  3. man 을 생각하지 않고 그냥 구글링을 하는 경우가 많이 보였다. 개인적으로 우선 man을 꼭 정독하길 바란다. 작동 원리 뿐만 아니라 연관된 함수들도 보여준다. 하물며 ascii 코드 같은 것들도 확인할 수 있다.
  4. ascii 를 이해한다면, int 값의 연산을 통해 문자열 확인이 될 것이다. tolower 과 toupper 는 충분히 처리할 수 있을 것이다.

< str 시리즈 >

  • strlen : 문자열의 문자 길이를 세고, 그 값을 반환한다.(size_t)
  • strlcpy : size_t의 dstize를 받아, 길이 만큼 src를 dst 에 붙여 넣고, 마지막에 0을 입력한다. 이때, dst 길이와 입력받은 dstsize에 따라 붙여 넣은 src의 길이를 리턴한다.
  • strlcat : size_t의 dstsize를 받아, 그 길이가 dst 문자열 길이보다 길다면 긴 만큼만 src를 dst에 붙여 넣고 리턴값을 반환한다.
    ⭐️ 단, 리턴값은 두 가지 경우로 쪼개진다.
    ⭐️ dstsize 가 실제 dst 길이보다 짧을 때 : src 길이 + dstsize를 리턴하고, 붙여넣기를 진행하지 않는다.
    ⭐️ dstsize 가 실제 dst 길이보다 길 때 : dst의 길이 + src 길이를 리턴하고, 붙여넣기를 진행한다.
  • strchr : 제공되는 문자(int c) 를 제공되는 문자열에서 발견하면, 그 위치부터 문자열 끝까지를 반환하는 포인터 주소를 반환한다.
  • strrchr : 제공되는 문자(int c) 를 제공되는 문자열에서 '뒤에서부터' 발견하며, 발견되면 해당 위치부터 문자열 끝까지를 반환하는포인터 주소를 반환한다.
  • strncmp : 제공되는 두 문자열을 n byte 만큼 만 비교하고, 다른 부분이 있을 때 해당 부분의 차이를 출력한다.
  • strnstr : 제공되는 문자열 두 개(haystack, needle)에서 needle에 저장된 문자열이 haystack에 정확하게 있는 부분부터 문장 끝까지를 출력하는 포인터를 반환한다. 이때, len byte 까지만 진행합니다.

해당 함수들의 포인트는?

  1. strlen에서 길이를 잴 때는 사람이 생각하는 기준에서 시작한다. 즉, 컴퓨터의 인덱스는 항상 0에서 시작하지만, 사용자는 1부터 시작한다는 차이가 있다. 놓치기 쉬운, 사소한 부분이다.
  2. strlcpy는 dst에 src 값을 넣는데, 이때 size_t를 정확하게 이해해야 하며, 복사 시에도 '\0' 값을 만나기 전까지나, 제공되는 size의 -1 한 값 만큼만 복사를 붙여넣게 된다.
  3. strlcat은 특히나 머리가 아픈 부분이 return 값 부분이다. 왜 그렇게 되는가를 반드시 찾고, 고민해보아야 한다.
  4. strchr, strrchr부터 본격적으로 포인터 를 이해해야 하는 부분이다. 그러나 동시에 프로토타입에서 찾을 수 있는 특징이 바로 const 라는 한정자가 붙어 있다는 점이다. const는 상수를 의미하며, 이 말은 해당 매개변수를 조작해서 바꾸어 버리는 행위(예를 들어, 시작 포인터를 바꾸는 등)를 하지 말아야 한다는 점이다.
  5. strnstr 은 needle이란 문자열을 전부 찾는 다는 점, 이때 size_t len 만큼만 해야 한다는 점만 고려하면 쉽게 구현이 가능하다.

size_t 란?

여러 해더에서 정의 내려져 있는 어떤 기기에서든 (이론상)사용 가능한 정수 최대 값 을 표현 가능한 자료형이다. 우리의 나무위키
이러한 자료형을 사용하는 이유는 우리가 사용하는 시스템마다 표현 가능한 데이터 크기가 다르다는 점 때문이다. 보통 int는 4바이트라고 이야기 하지만, 디바이스마다 이는 변경될 수 있고, 실제 다른 자료형도 마찬가지로 변동이 생긴다. 따라서 이를 맊기위해서 나왔으며, 보통 바이트 표시를 하는 경우, 거의 무조건적으로 사용된다. 자매품으로 ssize_t도 존재한다.
단, 사용시 주의할 점은, unsigned 형이라는 점이다. 즉, 음수가 존재하지 않는 만큼, 음수가 나오는 경우를 항상 고려하여야 한다.(if 조건에서 연산이 들어가 음수가 나올 경우를 항상 고려해여 한다.)

strlcat의 리턴 값이 의미하는 바는?

strlcat은 기존의 strncat과 유사한 기능이지만, 안정성을 중시하여 만들어진 함수이다. 그런데 독특하게도 return값은 size_t 값이 라는 점이며, 이것이 2가지 경우로 쪼개진다는 점을 들 수 있다.
1) size 가 strlen(dst) 보다 작다면, src + size를 반환한다.
2) 반대의 경우에는 dst + src 길이를 반환한다.
이렇게 되는 이유는 간단히 말하면, 붙여 넣는 과정에서 '얼마나 메모리 공간이 필요한가?' 를 보여주기 위함이다. 성공적으로 붙여넣기가 진행된다고 하더라도, 지정된 size가 부족하여 다 붙여넣지 못할 때는 2를 출력하여 필요한 양을 보여준다. 따라서 붙여넣기 성공 이후에라도 다 입력되지 않았을 때 어느정도 buffer사이즈를 줄지를 알려주는 것이다.
그러나 성공되지 않을 경우에는 어떨까? 현재 내가 넣으려는 대상(src), 넣으려는 글자수(len)는 이용자 중심으로 생각하면 그 크기를 고려할 수 있을 것이다.

정리 해보니 정말 길다.

앞으로 정리할 게 더 많다는 것은 생각보다 끔찍한(..) 일이긴 한 것 같다.
코드만 정리하면 참 편하겠지만, 결국 코드는 널려있고, 문제는 깊이를 이해해야 코드를 설명하고 이용을 극대화시킨다.

앞으로 Linux 서버 구축이나 기타 등등... 읽고 이해해야할 게 산더미라는 생각이 든다.

😮‍💨

재미는 있지만 ㅋㅋ...

profile
무한 도전. 무한 실패. 무한 성공.

0개의 댓글