Search
Duplicate
👀

libft, 깨고 나니 보이는 것들

간단소개
libft 과제를 진행하며 공부한 몇 가지 내용들을 정리해보았다!
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
C
42cursus
자료구조
Scrap
태그
9 more properties
본과정에서의 첫 번째 과제 libft!
처음 시작할 때는 어려운 것들 투성이었는데, 통과하고 보니 어느새 명확하게 알게 된 점들이 몇 가지 있었다.
man 매뉴얼을 모조리 뒤져가며, 평가를 받다 어버버해가며
열심히 시행착오를 겪으면서 얻은 소중한 지식들을 간략하게 정리해 보고자 한다

어떤 함수는 mem으로 시작하고 어떤 함수는 str으로 시작하는 이유

Part1 에서 구현하는 C 표준 라이브러리 함수 중 일부는 이름이 mem 또는 str 으로 시작한다. 둘의 차이는 해당 함수가 특정 바이트를 기준으로 처리하냐, 문자열을 기준으로 처리하냐 였다.
str 으로 시작하는 함수
문자열을 받아서, 이 문자열의 끝을 널문자(\0)로 판단한다. 따라서 문자열이 널문자를 만났는지 아닌지를 핵심적인 조건으로 활용하여 함수를 구현한다. strncmp 같이 size를 인자로 같이 받는 경우 size에 대한 조건 또한 포함되기도 한다.
mem 으로 시작하는 함수
메모리 영역을 가리키는 포인터 뿐만 아니라 처리해야 하는 바이트 수를 인자로 함께 받는다. 이 경우 널문자에 상관없이 인자로 받은 바이트 수가 핵심적인 조건이 된다. 이에 따라 char * 형 뿐만 아니라 void * 형으로 받을 수 있는 데이터도 다룰 수 있게 된다!

크기를 표현하는 자료형 size_t

구현해야 하는 함수들의 man 페이지를 들여다보면 문자열이나 메모리의 크기를 다루는 부분에선 어김없이 size_t 라는 낯선 자료형이 등장한다. 문자열의 길이를 반환하는 strlen 이나, len 바이트 만큼 특정 문자를 세팅하는 memset 함수에서처럼!
size_t strlen(const char *s); // 반환값이 size_t 타입 void *memset(void *b, int c, size_t len); // 매개변수가 size_t 타입
C
복사
C99 에 따르면 size_t 이론적으로 가능한 모든 타입의 객체의 최대 크기를 저장할 수 있는 unsigned 데이터 타입이다.
unsigned 타입은 0부터 시작해 양수 값만을 표현한다. 하지만 대응하는 signed 타입과 비교했을 때 자료형의 크기는 변하지 않기 때문에 음수 값을 표현하지 못하는 대신, 양수의 표현 범위가 넓어진다.
⇒ 따라서 양수로만 나타나는 문자열이나 메모리의 크기를 나타내기에 적합하다.
왜 문자열이나 메모리의 크기에 관해서는 unsigned 타입 대신 size_t를 사용할까
size_t 는 unsigned 정수 타입의 별칭(typedef)이다. 일반적으로 unsigned int 지만, unsigned long 또는 unsigned long long 이 될 수도 있다는 것! 특정 unsigned 타입 대신 size_t를 사용하면, 대상 플랫폼에서 가능한 가장 큰 객체의 크기를 나타내기에 충분하면서도 필요 이상으로 크지 않은 unsigned 정수를 선택할 수 있게 된다!
→ 이에 따라 size_t는 32비트 운영체제에서는 부호 없는 32비트 정수(unsigned int), 64비트 운영체제에서는 부호 없는 64비트 정수(unsigned long long)가 된다.
ssize_t …? 크기를 나타내는 signed 데이터 타입으로 음수까지 표현이 가능하다. read, write 같은 파일 입출력 함수는 실패 시에 -1을 반환하는데, 이를 위해서 음수까지 표현이 가능한 ssize_t를 반환형으로 사용한다.

overlap될 수 있다면 memcpy 대신 memmove를 사용해라?

두 함수 모두 특정 바이트만큼 메모리 영역을 복사할 때 사용되는 함수다.
그런데 memcpy의 man 페이지를 살펴보면, 복사 받는 영역과 복사할 영역이 겹치면 동작이 정의되지 않는다! 겹칠 가능성이 있는 경우, memmove를 사용해라! 라는 설명이 있다.
memcpy
메모리의 내용을 직접 복사한다. 따라서 두 메모리 영역이 겹치면, 복사 작업이 파괴적으로 일어날 가능성이 있다. 복사 받을 메모리 영역보다 복사할 메모리 영역이 앞에 위치하면서 두 영역이 겹치는 경우에 문제가 생기는데, 복사할 메모리 내용이 이미 바뀌어 기존의 값을 잃어버리는 탓에 잘못된 값이 복사되기 때문이다.
memmove
메모리의 내용을 임시저장소에 저장한 후 복사한다. 따라서 메모리 영역이 겹쳐도 복사 작업이 원하는 대로 진행되므로 더 안전하다고 할 수 있다.
다만 해당 과제에서 memmove를 구현하기 위해서 임시 저장소 대신 다른 방법을 찾아야 했다!!! 임시저장소를 생성하기 위한 malloc 함수를 사용할 수 없기 때문이었다.

파일을 대표하는 값 File Descriptor

Part2의 함수 중 일부는 인자로 받은 파일 디스크립터(fd)를 활용하여 구현한다.
파일 디스크립터(File Descriptor) 시스템으로부터 할당 받은, 파일을 대표하는 추상적인 값으로 0 또는 양의 정수 값을 갖는다. 유닉스 시스템에서는 프로세스가 파일에 접근하기 위해서 파일 디스크립터를 활용한다.
표준 입력(Standard Input), 표준 출력(Standard Output), 표준 에러(Standard Error)는 기본적으로 할당되는 파일 디스크립터로, 각각 0, 1, 2 정수 값이 할당된다.
프로세스 실행 중에 파일을 열면, 시스템은 해당 프로세스의 파일 디스크립터 숫자 중 사용하지 않은 가장 작은 값을 할당한다. 예를 들어 해당 프로세스에서 파일을 처음 열었다면 그 파일에는 파일 디스크립터로 숫자 3이 할당될 것이다!
파일 디스크립터는 고작 숫자인데, 어떻게 특정 파일을 대표할 수 있는 걸까
파일 디스크립터가 가리키는 파일에 어떻게 접근하는지를 이해하려면 다음 세 가지 테이블을 이해하는 것이 좋다!
File Descriptor Table 각 프로세스에서 현재 사용 중인 파일을 관리하기 위한 테이블이다. File Table을 가리키는 포인터를 담고 있는 배열이며, 이 배열의 index가 파일 디스크립터이다.
(Open) File Table 열린 파일의 읽기/쓰기 동작을 지원하기 위한 테이블으로, 파일이 열릴 때마다 하나씩 할당된다.
Inode Table 파일의 inode를 담고 있는 테이블이다. 같은 파일 당 하나씩 할당된다. inode 안에는 파일 종류와 권한, 크기, 데이터 블록을 가리키는 포인터 등의 파일 정보가 들어있다.
inode란?
그림과 함께 보면 이해가 쫌 더 잘 될 것이다.
이미지 출처 : File descriptor - Wikiwand
결국 파일 디스크립터는 File Descriptor Table의 인덱스였고, 해당 인덱스의 포인터를 통해 다음 두 테이블에 접근함으로써 파일 정보를 얻어낼 수 있는 것이었다!!

Linked ListArray 의 차이점

해당 과제의 Bonus part에서는 연결리스트를 다루는 함수들을 구현한다. 배열과 연결리스트는 둘다 데이터를 구조적으로 다루기 위한 ‘자료구조’의 한 종류라는데, 문득 정확히 어떤 차이가 있는 건지 궁금했다.
이미지 출처 : File descriptor - Wikiwand
배열(Array) 데이터들이 메모리 공간에서 연속적으로 저장되어 있는 자료구조이다.
배열에서 위치를 가리키는 숫자인 인덱스를 활용해 데이터에 쉽게 접근할 수 있다. → 데이터 탐색 시 O(1)의 시간복잡도를 가진다.
배열의 처음이나 중간에 데이터를 추가할 경우, 그 다음의 데이터들을 한 칸씩 뒤로 밀어야 한다. → 처음 및 중간에 데이터를 추가 시 O(n)의 시간복잡도를 가진다.
배열의 처음이나 중간의 데이터를 삭제할 경우, 그 다음의 데이터들을 한 칸씩 당겨와야 한다. → 처음 및 중간에 있는 데이터 삭제 시 O(n)의 시간복잡도를 가진다.
연결 리스트(linked list) 여러 개의 노드(데이터 묶음)들이 순차적으로 연결된 자료구조이다. 노드들은 메모리 공간에 흩어져 있으며, 각 노드가 이전 또는 다음 노드의 주소를 기억함으로써 연결된다.
특정 데이터에 접근하려면 처음부터 순차적으로 탐색해야 한다. → 데이터 탐색 시 O(n)의 시간복잡도를 가진다.
특정 데이터를 추가 및 삭제하는 경우, 노드가 기억하는 주소만 변경해주면 된다. → 데이터 추가 및 삭제 시 O(1)의 시간복잡도를 가진다.
배열과 연결리스트는 데이터 탐색과 추가 및 삭제에서 서로 가지고 있는 장단점이 달랐다. 무조건 배열이 좋다! 연결 리스트가 좋다! 판단하기 보다는 상황에 따라 유리한 자료구조를 택해야 겠다. 주가 되는 작업이 데이터 탐색이라면 배열을, 데이터 추가 및 삭제라면 연결 리스트를!
참고한 자료
잘못된 부분이 있다면 알려주세요