Libft


Libft는 자신이 사용할 라이브러리를 만드는 것을 목표로 한다.

  • norm규칙을 준수할 것
  • 뮬리넷을 통과해야지 컴파일 된다.
  • Makefile를 포함하여야 해야한다.
    • $(NAME), all, clean, fclean, re를 포함할 것 ++bonus
  • 메모리 관련 누수는 허락하지 않는다.
  • 전역변수사용 금지

시작하기 전 필요한 정보들

  1. 복잡한 함수의 구현을 위해 내부함수를 사용할 경우 static으로 선언하는 것이 좋다.
  2. libft.h내에 구현해놓은 함수를 활용하여 문제를 해결하자!
  3. 함수이름을 자주 틀리니 man에서 복사해서 사용하자
  4. man을 활용하여 함수의 구현목적을 확인할 것
  5. 너무 광범위한 null체크는 하지 말것! 함수의 목적상(null값 입력시 세크멘테이션 폴트가 출력되어애 하는 경우가 있음)
섹션 분류
1 실행 가능한 프로그램이나 셸 명령어
2 시스템 콜
3 라이브러리 함수
4 특별한 파일들(디바이스 파일)
5 파일 포맷
6 게임
7 규격 등
8 시스템 관리용 명렁어

part1은 libc(3) 표준 라이브러리 함수 구현이다.

파일디스크립터나 시스템콜 정보

void 포인터


C언어에서는 기본적으로 자료형이 다른 포인터끼리 메모리의 주소를 저장하면 컴파일경고가 발생한다.

int main(void)
{
 int a = 10;
 char b = 'b';
 int *int_point = &a;
 char *char_point = &b;

 int_point = char_point; // 오류발생
 
 return 0;
}

warning: assignment from incompatible pointer type

하지만 void 포인터 사용시 어떤 자료형의 포인터든 모두 저장할 수 있다.

  • var나 auto키워드 느낌? 범용적으로 사용 가능(가변적)
int main(void)
{
 int a = 10;
 char b = 'b';
 int *int_point = &a;
 char *char_point = &b;

 void *ptr;

 ptr = int_point; // 오류가 발생하지 않음
 ptr = char_point;

 int_point = ptr;
 char_point = ptr;

 return 0;
}

하지만 역 참조는 불가능 하다!

따라서 사용방법은 함수의 오버로딩 형식처럼 가변적인 포인터변수를 받아야 하는 함수들에 인자값으로 주로 사용된다.

  • void포인터는 역 참조(* 사용)할 수 없다고 했는데 그렇다면 어떻게 함수 내에서 작업을 수행하는지..

포인터의 주소값 자체가 메모리기 때문에 1바이트씩 읽을 수 있는 char 포인터형으로 강제형변환을 통해 사용한다..!

char 포인터를 메모리연산에 사용하는 이유는 아래에서 설명한다.

메모리 관련 unsigned char *를 사용하는 이유


C언어에서는 기본적으로 부호를 고려하지 않을 때 unsigned키워드를 붙인다.
따라서 상황에 맞게 음수를 고려하지 않을 경우에는 unsigned를 활용

그렇다면 1바이트은 char형의 부호를 고려하지 않는다면 사용되는 범위는 0~255(16진수로는 0x00 ~ 0xFF)가 된다.

  • (부호 비트를 사용하지 않아서)

즉, 1바이트(8비트)를 투명하게 사용할 수 있다는 점!

따라서 메모리 주소값을 처리하기 위해선 unsigned char 자료형을 사용하는 것이 기본이며 관례라고 한다!

따라서 void포인터 활용 시 unsigend char 포인터로 형 변환하여 안전하게 메모리에 접근하자!!

const char *p, char*const p 차이점


우선 const키워드는 상수 키워드로 변하지않고 원본을 유지하고자 할 때 사용된다.

함수 내에서 src즉, 원본을 포인터로 넘겨주어 값을 참조할 때 원본의 값은 변경 불가능하게 하여 보안성을 유지할 수 있다.

const char *p

포인터 p가 가르키는 자료형이 const char라는 것!

포인터 p는 readonly의 성격을 가지게 된다.

따라서 포인터의 역참조를 통한 값 변경은 불가능하다..!

int main(void)
{
 char str[20] = "Hello";
 const char *char_point = str;

 *(char_point+1) = 'h'; // 에러
  char_point++; // 가능 readonly
  
 return 0;
}
// 마찬가지로 char_point + 1또한 const char을 가리키는 포인터이다.

하지만 포인터자체에는 상수선언이 되지 않았기 때문에 char_point++과 같은 주소값 이동연산은 가능하다.

강제형 변환을 통해 값을 변경할 수 있지만 함수 설계 방면으로 개발자가 strcpy같은 함수에 src(원본)에 const을 걸어두게 된다면 이는 readonly로만 사용하라는 뜻이기 때문에 되도록 강제형변환하여 원본을 건들지 말자!

int main(void)
{
 char str[20] = "Hello";
 const char *char_point = str;
s
 *((char *)(char_point+1)) = 'o'; // 변경가능

 printf("%s", char_point);

 return 0;
}

char *const p;

p라는 상수는 char형을 가리키는 포인터라는 것이다!

즉, char형을 가리키는 주소값이 상수라는 것!(이를 상수 포인터라고 한다.)

int main(void)
{
 char str[20] = "Hello";
 char *const char_point = str;

 *(char_point+1) = 'o'; // 가능
 //char_point++; 불가능 

 printf("%s", char_point);

 return 0;
}

다시 말하자면 char_point라는 상수는 char형을 가리키는 포인터인 것이다.

따라서 가리키고 있는 주소값만 바뀌지 않는다면 역참조는 가능하다..!

const char *const p;

사용하지 않는것이 베스트..!

해석해보자면 상수 포인터이면서 가리키는 자료형또한 상수.. 즉, p가 가리키는 값또한 변경 불가능하고 p의 값 자체도 바꿀 수 없다!

  • ++ const char p와 char constp는 같은 뜻

size_t 자료형


size_t는 주로 문자열이나 메모리의 사이즈를 나타낼 때 사용된다!

사실 size_t는 unsigend int이다. typedef를 통해 size_t라는 이름을 정의해놓은 것!

  • 그렇다면 왜 size_t를 사용하는지?

size_t는 32,64비트 운영체제에 따라서 알맞게 부호가 없는 32비트 정수, 부호가 없는 64비트 정수의 자료형으로 정의된다.

하지만 unsigend int나 int는 운영체제가 64비트라고 해서 꼭 64비트의 정수 형태를 가지지 않는다!

따라서 c언어 내부 함수들 중 메모리, 문자열 사이즈 관련 인자들은 size_t를 사용한다!

ssize_t는 signed int형 자료형이다.

NULL..?


c언어에서는 0은 아스키코드 값이 0이며 표상에서 NULL을 표시하고 있다..!

따라서 c언어에선 0 == ‘\0’ == NULL이라고 할 수 있다.

하지만 숫자 0과 문자 ‘0’은 많이 다르니 주의하자!!

함수 포인터


함수포인터라고 함은 포인터와 마찬가지로 함수의 주소를 담는 포인터 변수이다.(4byte)

그럼 함수도 일종의 주소로 볼 수 있는 것인가?

  • 함수도 주소에 불과하다 따라서 함수포인터는 그냥 포인터와 동일하게 사용한다.
void (*fp)();

위의 처럼 선언된 함수포인터를 정리하면

  • void: 반환값 자료형
  • (*fp): 함수 포인터 식별자
  • (): 매개변수가 없음

즉, 반환값이 없고 매개변수도 없는 fp라는 이름을 가진 함수포인터

포인터의 성격 그대로 일치하는 함수만 가리킬 수 있다.

#include <stdio.h>

void print_void()
{
	printf("안녕하세요\n");
}

int main()
{
	void(*fp)() = print_void;
	fp();
	printf("%p\n", fp);
	printf("%p\n", print_void);

	return 0;
}

위 코드의 결과에서 %p로 찍은 주소값은 동일한 것을 확인할 수 있다.

++ 반환값이나 매개변수가 있는 함수 포인터

int (*fp)(int, int);

fp는 function pointer의 약어

메모리 영역? 할당?


메모리 [할당, 영역]에는 각각 [정적, 동적]으로 구분된다.

정적은 컴파일 단계에서 필요한 메모리공간을 할당하는 것이며 동적은 실행 단계에서 공간을 할당하는 것이다.

  • 정적영역의 code영역은 함수나 상수, 프로그램의 코드가 컴파일 단계에서 code영역에 저장된다.
  • 정젹영역의 전역변수와 정적변수가 저장되는 영역으로 컴파일 단계에서 메모리할당이 이루어 진다.
  • stack영역은 지역변수와 매개변수가 저장되며 해당 함수내에서 생성되고 사라진다.(따라서 동적영역)
  • heap은 동적할당을 받아오는 장소로 무조건적으로 free를 해제해줘야 한다. (동적영역)

여기서 헷갈리는 부분이 동적/정적의 영역과 정적/동적 할당에 대한 오해이다.

지역변수나 매개변수의 경우는 영역은 동적영역인 stack에 해당되지만 컴파일 단계에서 메모리 공간을 할당 받으니 정적할당이다.

할당과 영역을 헷갈리지 말것!

malloc함수는

int *ptr;
ptr = (int*)malloc(sizeof(int) * 5);

malloc은 sizof(int) -> 4byte * 5 즉 20만큼의 공간을 가진 void 포인터로 반환되는 값을 형변환을 통해 ptr에 넣어준다고 한다.

동적 할당(free의 개념)

c에서는 흔히 동적할당 된 메모리는 사용 후 반드시 해제해줘야 한다고 한다.

동적할당은 코딩하는 사람이 명확하게 malloc, calloc, realloc을 사용하여 직접 할당해준다.

그에 맞춰서 free(3)라는 함수는 사용자가 동적할당한 메모리만 해제해준다.

따라서 리터럴 값이나 상수값을 free하게 되면 [segmentation fault]가 뜬다.

free에서 등장하는 중요한 개념중 하나인dangling pointer!!

허상포인터라고 불리기도 하는 댕글링 포인터는 사용자가 free를 포인터가 가리키는 메모리 자체는 해제되었지만 ptr은 삭제되지 않아서 그대로 해제된 메모리 주소를 가리키는 것!

실수로 허상포인터를 사용한다면 프로그램에 큰 문제가 생길 수 있기 때문에 null체크와 free후 null할당이 중요하다.

Makefile


makefile이란..! 빌드를 도와주는 스크립트 파일

리눅스 환경에 있는 소스코드 프로그램들을 다운받아서 실행할려고 한다면 make이라는 명령어를 치게 되면 Makefile에 작성된 스크립트 순서대로 빌드를 수행한다.

좀 더 자세하게 들어가기 전에 실행파일이 만들어지는 과정에 대해 알고가는것이 좋다고 생각합니다아.

a.exe/a.out(실행파일) 은 소스파일을 각각 컴파일하여 .o라는 목적파일을 생성하고 만들어진 목적파일들을 한곳으로 묶는 링킹과정을 통해 만들어 진다..!

좀 더 자세하게 들어가자면 .i, .s과정이 있지만 makefile부분이 아니니 나중에 다루기로 하겠다

cli상에서 컴파일, 링킹을 하여 실행파일을 생성할 땐 간단하게 gcc,g++,clang을 통해 만들지만 해당 내부의 동작방식을 이해해야 한다!

gcc -c -o main.o main.c

위 명령어는 gcc에 -c플래그를 붙여서 컴파일만 실행할 수 있다. 즉, main.o 파일을 빌드하는 것
-c 플래그를 통해 목적파일을 생성하면 main과 같은 이름으로 .o가 생기지만 이름을 꼭 지정해주자..

gcc -o a.out main.o

명령은 gcc지만 gcc내부적으로 링커를 실행하여 실행파일을 생성한다..! (-o 플래그는 이름 변경)

그렇다면 makefile을 사용해야하는 이유!
Incremental build

Incremental build은 makefile은 똑똑하게 의존성이 있는 대상들만을 추려서 다시 빌드가 가능하기 때문에 덩치가 큰 작업의 경우 매우 유용하다!!

  • 작성방식!
    작성방식은 쉘스크립트와 유사하다..

Rule block의 구조

<Target>: <Dependencies>
  <Recipe>
  • target: 빌드 대상 이름! 통상적으로 최종적으로 생성해내는 파일명을 쓴다고 한다.
  • dependencies: target이 의존하는 target이나 파일목록 target을 만들기전 먼저 만들고 빌드 대상(target)을 생성
  • recipe: 빌드 대상을 생성하는 명령. 여러줄로 작성이 가능하고 반드시 tap으로 구분해야한다.

make를 실행하게 되면 Makefile내부 .으로 시작하는 target을 제외한 가장 상단에 있는 target이 실행된다.

  • 내장 규칙!

Makefile에서는 자주 사용되는 빌드 규칙들은 굳이 기술하지 않아도 자동으로 처리해준다..! (소스파일을 컴파일하여 목적파일로 만드는 규칙)

하지만 target에 대한 헤더파일을 추적하지 않기 때문에 dependencies까지는 명시해줘야 한다.(헤더파일을)

위에서 다룬 main.c파일에 의존성을 가지는 fun.c에 헤더파일이 존재한다면 아래와 같이 룰블럭을 작성한다.

a.out: main.o fun.o
  gcc -o a.out main.o fun.o

main.o: fun.h main.c
  fun.o: fun.h fun.c
  • 변수 사용법

사용가능한 변수명은 make -p를 통해 확인할 수 있다.

CC=gcc
CFLAG= -Wall -Werror -Wextra
OBJS= main.o fun.o
TARGET= a.out

$(TARGET): $(OBJS)
  $(CC) -o $@ $(OBJS)

main.o: fun.h main.c
fun.o: fun.h fun.c
  • $@은 현재 target의 이름을 나타내는 자동변수
  • $^은 현재 target이 의존(Dependencies)하는 대상들의 전체 목록
  • $<은 첫 번째 depend의 파일 이름
  • $?은 현재 target이 의존하는 대상들중 변경된 목록

위의 Makefile을 해석해보면 a.out을 만들기 위한 의존성 파일들은 main.o, fun.o가 존재하고 a.out에 대한 레시피는 밑에 나와 있지만 먼저 의존성파일들을 먼저 생성한다.
의존성 생성파일에 대한 룰블럭또한 아래에 나와 있으며 해당 레시피가 존재하지 않는 이유는 makefile의 내부 규착애 의해 자동 빌드된다.
다시 돌아가서 $(TARGET)(a.out)의 recipe는 $(CC) gcc컴파일을 사용하여 -o(이름 변경)으로 target이름으로 변경하고 필요한 오브젝트 목록은 OBJS로 참조한다.

여기서 변수명을 사용하여 만약 개발자가 a.out이 아닌 다른 이름을 사용하고 싶다면 맨 위 변수 선언부에서 이름만 변경하면 아래의 $(TARGET)의 이름들은 자동변경되게 된다.

  • 외부 헤더파일 포함시키는 법

외부 헤더파일을 포함시킬려면 gcc 뒤에 -I플래그가 붙어야 한다.(표준 디렉토리가 아닌 헤더파일의 경우 디렉토리 위치 지정)

gcc main.c -I ./include
  • 라이브러리 생성 방법
ar r libft.a *.o

현재 루트에 존재하는 .o 목적 파일들로 libft.a라는 라이브러리 파일을 만든다.

ar s libft.a

함수의 인덱스를 추가한다.

++ ar t를 통해 내부 오브젝트 파일을 표시할 수 있다.

라이브러리를 생성할 때 마다 주의 표시가 뜨기 때문에 c플래그를 통해 제거할 수 있다.

ar rcs
  • clean

자주 사용되는 룰 블럭의 모습이며 make뒤에 플래그처럼 붙여 매크로로 사용 가능하다.

target과 목적파일들을 전부 삭제하는 역할을 한다. Dependencies가 존재하지 않으며 recipe만 존재

clean:
  rm -rf $(OBJS) $(TARGET)
  • all
all: libft.a

all은 실제로 생성하는 것이 아닌 무엇이 최종 target인지 명확하게 지정해주는 역할이다.

make all을 실행할 경우 libft.a를 만들기 시작..

  • .PHONY

실제 타켓의 이름을 가리키는 것이 아닌 가짜 타켓을 가르킨다..!

사용하는 이유는 동일한 이름의 충돌을 피하고 성능향상을 위해 사용한다.

clean:
  rm main.o main.c

위 매크로를 실행하면 rm명령은 clean이라는 이름의 파일을 생성하지않기 때문에 clean이란 이름의 파일이 존재하지 않을 것이다. rm명령은 make clean 수행할때 마다 매번 실행되게 된다.

하지만 디렉토리에 clean이라는 이름의 파일을 생성하게 되면 clean은 제대로 동작하지 않게 되고 이러한 문제를 해결하기 위해선 .PHONY를 사용하여 전제조건을 만들어 준다.

실제 라이브러리 생성!

TARGET= libft.a
CC= gcc
CFLAG= -Wall -Werror -Wextra
SRCS= main.c fun.c
OBJS= $(SRCS:.c=.o)

.c.o: $(SRCS)
  $(CC) $(CFLAGS) -c $<

all: $(TARGET)

$(TARGET): $(OBJS)
  ar rcs $(TARGET) $(OBJS)

clean:
  rm -f $(OBJS)

fclean: clean
  rm -f $(TARGET)

re: fclean all

.PHONY all clean fclean re
  • relink 관련 추가 예정

Part1

C의 표준라이브러리 Libc의 함수들을 이미테이션하여 만든다.

외부 함수를 필요로 하지 않음

함수들은 크게 4가지로 분류할 수 있다.

  • 해당 값이 맞는지 확인하는 is계열 함수
    • ft_isalpha, ft_isdigit, ft_isalnum, ft_isascii, ft_isprint
    • (추가)ft_isspace, ft_isupper, ft_islower
  • 문자를 변환해주는 to계열 함수
    • ft_tolower, ft_toupper, ft_atoi
    • (추가)ft_
  • 메모리 관련 mem 함수
    • ft_memset, ft_bzero, ft_memcpy, ft_memmove, ft_memchr, ft_memcmp, ft_calloc
    • (추가)ft_
  • 문자열 관련 str 함수
    • ft_strlen, ft_strlcpy, ft_strlcat, ft_strrchr, ft_strnstr, ft_strdup
    • (추가)ft_

추가적으로 malloc을 사용하여 ft_calloc과 ft_strdup를 구현한다.

ft_isalpha

#include <ctype.h>

int
isalpha(int c);

ISALPHA(3) 표준 C 라이브러리 함수이다.

isupper(3) or islower(3)가 참인 경우 참을 번환한다.

isuperr, islower은 part1 추가파트에 기재하였습니다.

ft_isdigit

#include <ctype.h>

int
isdigit(int c);

ISDIGIT(3) 표준 C 라이브러리 함수이다.

숫자 ‘0’~’9’까지를 검사하는 함수이다.(문자형 숫자로 아스키 코드상 48~57까지를 말한다.)

같은 기능을 하는 함수로 isnumber이 있다.

ft_isalnum

#include <ctype.h>

int
isalnum(int c);

ISALNUM(3) 표준 C 라이브러리 함수이다.

isalpha(3) or isdigit(3)가 참인 경우 참을 반환한다.

ft_isascii

#include <ctype.h>

int
isascii(int c);

ISASCII(3) 표준 C 라이브러리 함수이다.

int형으로 들어온 인자가 아스키코드에 포함이 된다면 참을 반환한다.

ft_isprint

#include <ctype.h>

int
isprint(int c);

ISPRINT(3) 표준 C 라이브러리 함수이다.

공백을 포함한 모든 출력 가능한 문자가 들어온다면 참을 반환한다.

아스키 코드표 상으로 32~126까지이다.

(추가)ft_islower

#include <ctype.h>

int
islower(int c);

ISLOWER(3) 표준 C 라이브러리 함수이다.

영어의 소문자를 검사하여 참을 반환한다.

(추가)ft_isupper

#include <ctype.h>

int
isupper(int c);

ISLOWER(3) 표준 C 라이브러리 함수이다.

영어의 대문자를 검사하여 참을 반환한다.

(추가)ft_isspace

#include <ctype.h>

int
isspace(int c);

ISSPACE(3) 표준 C 라이브러리 함수이다.

white-space를 검사하여 참을 반환하는 함수이다.

wite-space란, 컴퓨터나 콘솔에서 공백을 표현하는 문자들들 의미한다.

``\t''   ``\n''    ``\v''    ``\f''    ``\r''    `` ''

아스키 코트표 상으로 9~13 그리고 공백(32)를 말한다.

ft_tolower

#include <ctype.h>

int
tolower(int c);

TOLOWER(3) 표준 C 라이브러리 함수이다.

들어온 인자가 대문자라면 소문자로 반환한다.

isupper(3)을 사용하여 풀이한다.

ft_toupper

#include <ctype.h>

int
toupper(int c);

TOUPPER(3) 표준 C 라이브러리 함수이다.

들어온 인자가 소문자라면 대문자로 반환한다.

islower(3)을 사용하여 풀이한다.

ft_atoi

#include <stdlib.h>

int
atoi(const char *str);

ATOI(3) 표준 C 라이브러리 함수이다.

들어온 문자열의 초기부분을 int형으로 반환한다.

즉, 들어온 문자열을 숫자로 반환하는 함수

  • 문자열의 처음 공백 부분은 전부 무시한다.
  • 문자열은 처음 +,-기호를 인식한다.
  • 앞선 규칙들을 포함하여 숫자만을 인식하여 int으로 변환한다.

isspace(3), isdigit(3)를 사용하여 풀이한다.

ft_memset

#include <string.h>

void *
memset(void *b, int c, size_t len);

MEMSET(3) 표준 C 라이브러리 함수이다.

메모리의 값을 특정값으로 세팅할 수 있는 함수이다.

void형 포인터인자로 받아서 (내부에선 메모리에 접근하기 위해 unsigned char *를 사용) len만큼 c로 설정한다.

  • 성공한다면 원래 메모리 주소를 반환하고 실패한다면 NULL을 반환한다.
  • int c초기화로 설정하는 값은 인자가 int로 되어 있지만 내부에서 unsigned char로 변경되니 주의
    • 이말은 즉슨, int형 배열의 경우 내부에서는 메모리 블럭으로 4byte씩 [00000000][00000000][00000000][00000001] -> 1 를 기대하겠지만 memset내부에서 메모리블럭을 1byte로 구해버려서 [00000001][00000001][00000001][00000001] -> 16843009 값으로 초기화 되어버린다.
  • 위 조건에 맞춰서 만들어진 함수이기 때문에 이름에 맞게 0이나 NULL로 초기화할 것!

ft_bzero

#include <strings.h>

void
bzero(void *s, size_t n);

BZERO(3) 표준 C 라이브러리 함수이다.

memset과 비슷한 기능을 하지만 인자값이 없는 대신 전부 0으로 초기화한다.

사용하지 말것! memset을 사용하자..

ft_calloc

#include <stdlib.h>

void *
calloc(size_t count, size_t size);

MALLOC(3) 표준 C 라이브러리 함수이다.

count(개수)만큼 size(크기)의 메모리공간을 동적으로 할당해주는 함수이다.

내부에서 malloc을 사용하여 구현할 수 있다.

  • 내부 공간을 전부 0으로 초기화 한다.

NULL검사는 필수

ft_memcpy

#include <string.h>

void *
memcpy(void * dst, const void * src, size_t n);

MEMCPY(3) 표준 C 라이브러리 함수이다.

dst에 src로부터 n만큼 copy한다.

  • dst src가 겹치면 동작이 되지 않는다고 한다.
    • 겹치는 경우엔 memmove(3)을 사용할 것
  • char형 배열로 복사하는 경우는 ‘\0’까지 복사해야하니 +1을 생각할 것!

ft_memmove

#include <string.h>

void *
memmove(void *dst, const void *src, size_t len);

MEMMOVE(3) 표준 C 라이브러리 함수이다.

dst에 src로부터 lne만큼 copy한다.

  • memcpy와 다른 점은 두 문자열이 겹칠 수 있으머 복사는 항상 파괴되지 않음

ft_memchr

#include <string.h>

void *
memchr(const void *s, int c, size_t n);

MEMCHR(3) 표준 C 라이브러리 함수이다.

s에 c의 위치를 찾아서 반환해준다.

  • 찾은 바이트를 반환하거나 n(갈이)내에서 없다면 NULL을 반환한다.
  • 반환형이 void이기 때문에 메모리 값을 조회한 다음 다시 형변환이 필요하다.

ft_memcmp

#include <string.h>

int
memcmp(const void *s1, const void *s2, size_t n);

MEMCMP(3) 표준 C 라이브러리 함수이다.

두 문자열이 동일하면 0을 반환하고 그렇지 않으면 처음 두 개의 서로 다른 바이트 사이의 차이를 반환합니다.

ft_strlen

#include <string.h>

size_t
strlen(const char *s);

STRLEN(3) 표준 C 라이브러리 함수이다.

문자열을 인자로 받아 문자열의 길이를 반환한다.

‘\0’을 제외한 길이

ft_strdup

#include <string.h>

char *
strdup(const char *s1);

STRDUP(3) 표준 C 라이브러리 함수이다.

s1과 같은 메모리를 할당하고 s1의 내용을 복사한 포인터를 반환한다.

ft_strlcat

#include <string.h>

size_t
strlcat(char * dst, const char * src, size_t dstsize);

STRLCAT(3) 표준 C 라이브러리 함수이다.

strlcat은 strcat의 안전성 버전으로 길이가 추가되어 오류발생률을 줄였다.

strlcat은 dst뒤에 src를 dstsize만큼 붙여준다.

  • dstsize가 dest이하라면 문자열을 붙이는 과정은 생략하고 src+dstsize길이를 반환한다.
  • dstsize가 더 크다면 -1만큼 src를 붙이고 ‘\0’를 넣어준다.(src + dst길이 반환)

ft_strlcpy

#include <string.h>

size_t
strlcpy(char * dst, const char * src, size_t dstsize);

STRLCPY(3) 표준 C 라이브러리 함수이다.

strlcpy은 마찬가지로 길이를 추가하여 오류발생을 줄였다.

dst에 src를 복사한다. 조건은 dstsize보다 작거나 ‘\0’을 만나거나

  • destsize - 1만큼 복사하거나 ‘\0’만나기 전까지 복사하여 dst에 저장한다.
  • src의 문자열의 길이를 반환한다.

ft_strchr

#include <string.h>

char *
strchr(const char *s, int c);

STRCHR(3) 표준 C 라이브러리 함수이다.

s문자열에서 c를 찾고 해당 위치를 반환하는 함수이다.

존재하지 않으면 null을 반환한다.

memchr(3), strlen(3)을 사용하면 좀 더 쉽게 풀 수 있다.

ft_strrchr

#include <string.h>

char *
strrchr(const char *s, int c);

STRRCHR(3) 표준 C 라이브러리 함수이다.

strchr의 반대버전이다.

즉, 뒤에서 부터 문자 검사를 시행한다.

ft_strnstr

#include "libft.h"

char	*ft_strnstr(const char	*haystack, const char	*needle, size_t	len)

STRNSTR(3) 표준 C 라이브러리 함수이다.

strnstr함수는 haystack문자열에서 len길이중에서 needle문자열을 찾아서 해당 문자열의 시작위치를 반환한다.

  • 찾지 못한 경우에는 null반환
  • needle이 비어있는 경우에는 haystack반환

ft_strncmp

#include <string.h>

int
strncmp(const char *s1, const char *s2, size_t n);

STRCMP(3) 표준 C 라이브러리 함수이다.

memcmp(3)와 유사한 기능을 수행하지만 ‘\0’을 만나면 종료하기 때문에 메모리와 문자열은 다른 함수이다.

  • 길이나 null문자를 생각하여 예외처리를 해야함

내부에서 unsigned char로 캐스팅이 필요함

Part2

libc에 포함되어 있지 않거나 다른 형식으로 포함된 함수들을 재구현한다.

최대한 part1의 함수를 활용해보자!

함수들은 그게 두가지로 구분된다.

  • 문자열 관련 함수
    • ft_strmaip, ft_substr, ft_strjoin, ft_strtrim, ft_split, ft_itoa, ft_striteri
    • (추가)ft_numlen, (추가)ft_strwordcnt
  • 파일 디스크립터(시스템 콜 write함수 관련)
    • ft_putchar_fd, ft_putstr_fd, ft_putendl_fd, ft_putnbr_fd

ft_strmapi

"libft.h"

char	*ft_strmapi(char const	*s, char	(*f)(unsigned int, char))

원본 문자열에서 함수f를 적용하여 생성한 문자열 반환하는 함수

  • 문자열 s를 순회하며 함수’f’를 적용하고 새로운 문자열을 생성한다.
  • null처리 중요!

ft_substr

"libft.h"

char *ft_substr(char const *s, unsigned int start, size_t len);

substr는 s의 문자열에서 start의 길이에서 len길이만큼 복사하여 새로운 문자열을 반환하는 함수이다.

  • s의 null처리
  • s의 길이가 start의 길이보다 작을 경우 빈 문자열 반환
  • 문자열이기 때문에 ‘\0’문자 처리 +1 처리해야함

ft_strjoin

"libft.h"

char *ft_strjoin(char const *s1, char const *s2);

strjoin의 경우 s1뒤에 s2를 붙인 새로운 문자열을 반환한다.

  • s1과 s2의 길이체크 및 널문자 삽입

ft_strtrim

#include "libft.h"

char	*ft_strtrim(char const	*s1, char const	*set)

strtrim은 s1문자열의 양끝에서 set문자열을 제거하는 함수이다.

ft_strchr(3)을 사용하여 set을 검사하여 풀이한다.

ft_split

#include "libft.h"

char	**ft_split(char const	*s, char	c)

split함수는 문자열을 하나와 구분자를 입력받아서 문자열을 구분자로 구분하여 2차원 문자열 배열로 반환하는 함수이다.

  • 문자열에 존재하는 단어(set으로 구분된다.)의 길이를 파악한다.
  • 앞서 공부한 split과 다르게 화이트 스페이스를 따지지 않고 구분자를 기준으로 생각한다.
  • 동적할당이 제대로 이루어져야 하기 때문에 크기에 맞게 동적할당한다.

내부 함수가 많이 필요하니 static선언하거나 함수를 추가하여 사용할 것

(추가)ft_strwordcnt

#include "libft.h"

size_t	ft_strwordcnt(const char	*s, char	c)

strwordcnt는 문자열과 구분자를 인자로 받아 문자열에 구분자로 구분되는 단어의 개수를 반환한다.

ft_itoa

#include "libft.h"

char	*ft_itoa(int n);

itoa함수는 숫자를 문자열로 변환하여 반환해주는 함수이다.

  • 0에 대한 예외 처리와 숫자길이를 구하는 함수가 필요함

(추가)ft_numlen

#include "libft.h"

size_t	ft_numlen(int n);

numlen은 숫자의 길이를 반환해준다.(‘-‘포함)

ft_striteri

#include "libft.h"

void	ft_striteri(char *s, void (*f)(unsigned int, char*))

striteri함수는 문자열을 순회하며 함수 f를 적용사키는 함수이다.

  • 순회하면서 적용시키는데 가변 함수 f인자값이 (char *)인 이유는 반환값이 void이며 주소값을 전달하여 함수에서 직접 함수를 적용할 수 있도록 포인터로 주소를 넘겨준다.

ft_putchar_fd

#include "libft.h"

void	ft_putchar_fd(char c, int fd)

putchar_fd함수는 해당하는 fd(파일디스크립터 값)으로 열려있는 스트림에 바이트열을 c문자를 흘려보내는 함수이다.

ft_putstr_fd

#include "libft.h"

void ft_putstr_fd(char *s, int fd)

putstr_fd함수는 해당하는 fd(파일디스크립터 값)으로 열려있는 스트림에 바이트열을 문자열 s를 흘려보내는 함수이다.

ft_putendl_fd

#include "libft.h"

void	ft_putendl_fd(char *s, int fd) 

putendl_fd함수는 putstr_fd함수에서 줄바꿈(‘\n’)을 추가한 함수이다.

ft_putnbr_fd

#include "libft.h"

void	ft_putnbr_fd(int n, int fd)

putnbr_fd함수는 똑같은 방식으로 숫자로 바꾸서 스트림에 흘려보낸다.

Bonus Part

보너스 파트는 리스트관련 함수들로 구성되어 있다.

기본적인 단일연결리스트의 형태의 구조체를 다루며 관련 함수를 만들어 본다.

typedef struct s_list
{
    void          *content;
    struct s_list *next;
}              t_list;

makefile부분에도 추가할 것

ft_lstnew

#include "libft.h"

t_list	*ft_lstnew(void	*content)

lstnew함수는 리스트하나를 동적할당하고 content를 넣어준뒤 next를 null을 가리키게 하는 함수이다.

  • 단일연결리스트의 개념에 대해 공부해야 한다.

ft_lstdelone

#include "libft.h"  

void	ft_lstdelone(t_list *lst, void (*del)(void *))

lstdelone함수는 해석 그대로 lst(리스트) del(삭제) one(하나) 즉, 연결리스트중 하나를 지우는 함수이다.

  • 첫 번째 인자값으로 받은 요소를 두번째 인자 함수포인터로 해제하고 메모리 자체도 해제한다.

next 다음을 가리키는 포인터는 해제하면 안된다.

ft_lstclear

#include "libft.h"

void	ft_lstclear(t_list **lst, void (*del)(void *))

lstcleat함수는 인자값으로 받은 리스트에 접근하여 리스트의 모든 content를 삭제하고 해제합니다.

  • 연산자 우선순위 주의하기
  • 댕글링 포인터 초기화 필수

ft_lstadd_front

#include "libft.h"

void	ft_lstadd_front(t_list **lst, t_list *new)

lstadd_front는 리스트 head부분에 new를 추가하는 함수이다.

  • null 체크 후 head부분이기 때문에 *lst로 접근하여 연결한다.

ft_lstadd_back

#include "libft.h"

void	ft_lstadd_back(t_list **lst, t_list *new)

lstadd_back함수는 리스트의 가장 마지막 부분 tail에 new를 추가하는 함수이다.

  • 마지막 요소에 접근하는 lstlast를 사용해도 되고 반복문으로 접근해도 된다.
  • head부분이 null인 경우 예외처리 필요!

ft_lstsize

#include "libft.h"

int	ft_lstsize(t_list *lst)

lstsize함수는 리스트의 사이즈를 반환해주는 함수이다. 다시말해서 리스트에 연결된 요소가 몇개 인지 파악한다.

  • 반환값이 int형이기 때문에 while문으로 쉽게 반환이 가능하다.

ft_lstiter

#include "libft.h"

void	ft_lstiter(t_list *lst, void (*f)(void *))

lsttiter함수는 lst를 순회하며 모든 content요소에 함수 f()를 적용시키는 함수이다.

  • 같은 맥락으로 반복적으로 순회하며 요소에 접근한다. size함수와 같음

ft_lstmap

#include "libft.h"

t_list	*ft_lstmap(t_list *lst, void *(*f)(void *), void (*del)(void *))

lstmap함수는 인자인 lst리스트의 content값을 함수 f()적용시켜서 새로운 리스트를 반환하는 함수이다.

  • 지금까지 만들어진 lst함수들을 활용하여 풀이한다.

ft_lstlast

#include "libft.h"

t_list	*ft_lstlast(t_list *lst)

lstlast함수는 리스트의 마지막요소를 반환하는 함수이다.

배운점

동아리 프로젝트나 코로나로 인해 클러스터를 많이 못가서 단기간에 과제를 하지 못했지만 그 만큼 시간이 걸려서 풀다보니 궁금한점이 많이생겨서 이번 포스팅 덩치가 조금 커진 것 같다..

우선 가장 크게 배운점은 메모리, leak, 문자열등.. c언어 조금 한다고 생각했던… 나에게 조금 어려웠다 ㅠㅠ..

다양한 함수를 추가해보고 싶었는데 이번 과제를 제출하고 나서도 이후 과제에서 필요한 부분이 있으면 함수로 추가해볼 생각이다.

++ 평가 후 얻은 정보

  • 배열 포인터 부분 수정

  • 조건문 우선 순위 수정

태그: ,

카테고리:

업데이트:

댓글남기기