1. 디렉토리 구조#

/home/qhddybuntu/workspace/projects/os-2024/book/projects/202221014
└── kv_store
    ├── src
    │   ├── main.c
    │   ├── kv_store.c
    │   └── kv_store.h
    ├── build
    ├── database.txt
    └── Makefile

1.1 디렉토리 생성#

cd /home/qhddybuntu/workspace/projects/os-2024/book/projects/202221014
mkdir kv_store
cd kv_store
mkdir src build
touch src/main.c src/kv_store.h src/kv_store.c database.txt Makefile

2. 프로그램 작성#

main.c

#include <stdio.h>    // 표준 입출력 라이브러리
#include <stdlib.h>   // 표준 라이브러리 (메모리 할당, 변환 등)
#include <string.h>   // 문자열 처리 라이브러리
#include "kv_store.h" // kv_store의 함수와 구조체 선언 헤더 파일

KeyValue* kv_head = NULL; // Key-Value 저장소의 헤드 포인터

// 명령어를 실행하는 함수
void execute_command(char* command) {
    char* token = strtok(command, ",");         // 명령어와 인자를 ','로 구분
    if (strcmp(token, "cv p") == 0) {           // 명령어가 "cv p"인 경우 (삽입)
        int key = atoi(strtok(NULL, ","));      // 키를 정수로 변환
        char* value = strtok(NULL, ",");        // 값을 문자열로 받음
        if (!insert_kv(&kv_head, key, value)) { // 삽입 실패 시 메시지 출력
            printf("Failed to insert key: %d\n", key);
        }
    } else if (strcmp(token, "cv g") == 0) {    // 명령어가 "cv g"인 경우 (검색)
        int key = atoi(strtok(NULL, ","));      // 키를 정수로 변환
        char* value = get_kv(kv_head, key);     // 키에 해당하는 값을 검색
        if (value) {
            printf("%d,%s\n", key, value);      // 값을 찾으면 출력
        } else {
            printf("K not found (K=%d)\n", key);// 값을 찾지 못하면 메시지 출력
        }
    } else if (strcmp(token, "cv d") == 0) {    // 명령어가 "cv d"인 경우 (삭제)
        int key = atoi(strtok(NULL, ","));      // 키를 정수로 변환
        if (!delete_kv(&kv_head, key)) {        // 삭제 실패 시 메시지 출력
            printf("K not found (K=%d)\n", key);
        }
    } else if (strcmp(token, "cv c") == 0) {    // 명령어가 "cv c"인 경우 (전체 삭제)
        clear_db(&kv_head);                     // 데이터베이스를 초기화
        printf("database is empty\n");
    } else if (strcmp(token, "cv a") == 0) {    // 명령어가 "cv a"인 경우 (전체 출력)
        if (kv_head == NULL) {
            printf("database is empty\n");      // 데이터베이스가 비어있으면 메시지 출력
        } else {
            print_all_kv(kv_head);              // 데이터베이스의 모든 값을 출력
        }
    } else {
        printf("Invalid command\n");            // 알 수 없는 명령어에 대한 메시지 출력
    }
}

// 메인 함수
int main() {
    if (load_db(&kv_head) == -1) {              // 데이터베이스 로드
        printf("Failed to load database\n");    // 로드 실패 시 메시지 출력
    }

    char command[256];                          // 명령어를 저장할 배열
    while (1) {
        printf("cv> ");                         // 프롬프트 출력
        if (!fgets(command, 256, stdin)) break; // 사용자로부터 명령어 입력받음
        command[strcspn(command, "\n")] = 0;    // 입력된 명령어에서 개행문자 제거
        execute_command(command);               // 명령어 실행
    }

    if (save_db(kv_head) == -1) {               // 데이터베이스 저장
        printf("Failed to save database\n");    // 저장 실패 시 메시지 출력
    }

    free_kv(kv_head);                           // 메모리 해제
    return 0;
}

kv_store.c

#include "kv_store.h" // kv_store의 함수와 구조체 선언 헤더 파일
#include <stdio.h>    // 표준 입출력 라이브러리
#include <stdlib.h>   // 표준 라이브러리 (메모리 할당, 변환 등)
#include <string.h>   // 문자열 처리 라이브러리

// Key-Value 쌍을 생성하는 함수
KeyValue* create_kv(int key, const char* value) {
    KeyValue* new_kv = (KeyValue*)malloc(sizeof(KeyValue)); // 새로운 Key-Value 쌍을 위한 메모리 할당
    if (new_kv) {                                           // 메모리 할당 성공 시
        new_kv->key = key;                                  // 키 값 설정
        strncpy(new_kv->value, value, MAX_VALUE);           // 값 설정
        new_kv->next = NULL;                                // 다음 포인터 초기화
    }
    return new_kv;                                          // 생성된 Key-Value 쌍 반환
}

// Key-Value 쌍을 메모리에서 해제하는 함수
void free_kv(KeyValue* head) {
    KeyValue* tmp;
    while (head) {                                          // 리스트의 모든 요소를 순회하며
        tmp = head;                                         // 현재 요소를 임시 변수에 저장
        head = head->next;                                  // 다음 요소로 이동
        free(tmp);                                          // 현재 요소 메모리 해제
    }
}

// Key-Value 쌍을 리스트에 삽입하는 함수
int insert_kv(KeyValue** head, int key, const char* value) {
    KeyValue* new_kv = create_kv(key, value);               // 새로운 Key-Value 쌍 생성
    if (!new_kv) return 0;                                  // 생성 실패 시 0 반환

    new_kv->next = *head;                                   // 새로운 요소의 다음 포인터를 현재 헤드로 설정
    *head = new_kv;                                         // 헤드를 새로운 요소로 업데이트
    return 1;                                               // 삽입 성공 시 1 반환
}

// 키 값에 해당하는 값을 검색하는 함수
char* get_kv(KeyValue* head, int key) {
    while (head) {                                          // 리스트의 모든 요소를 순회하며
        if (head->key == key) return head->value;           // 키 값이 일치하면 값을 반환
        head = head->next;                                  // 다음 요소로 이동
    }
    return NULL;                                            // 키 값이 없으면 NULL 반환
}

// 키 값에 해당하는 Key-Value 쌍을 삭제하는 함수
int delete_kv(KeyValue** head, int key) {
    KeyValue *tmp = *head, *prev = NULL;                    // 현재 요소와 이전 요소를 위한 포인터
    while (tmp) {                                           // 리스트의 모든 요소를 순회하며
        if (tmp->key == key) {                              // 키 값이 일치하면
            if (prev) {                                     // 이전 요소가 있으면
                prev->next = tmp->next;                     // 이전 요소의 다음 포인터를 현재 요소의 다음 포인터로 설정
            } else {                                        // 이전 요소가 없으면 (헤드인 경우)
                *head = tmp->next;                          // 헤드를 현재 요소의 다음 요소로 업데이트
            }
            free(tmp);                                      // 현재 요소 메모리 해제
            return 1;                                       // 삭제 성공 시 1 반환
        }
        prev = tmp;                                         // 이전 요소를 현재 요소로 업데이트
        tmp = tmp->next;                                    // 다음 요소로 이동
    }
    return 0;                                               // 키 값이 없으면 0 반환
}

// 데이터베이스를 초기화하는 함수
void clear_db(KeyValue** head) {
    free_kv(*head);                                         // 모든 요소 메모리 해제
    *head = NULL;                                           // 헤드를 NULL로 설정
}

// 모든 Key-Value 쌍을 출력하는 함수
void print_all_kv(KeyValue* head) {
    while (head) {                                          // 리스트의 모든 요소를 순회하며
        printf("%d,%s\n", head->key, head->value);          // 키 값과 값을 출력
        head = head->next;                                  // 다음 요소로 이동
    }
}

// 데이터베이스를 파일에서 로드하는 함수
int load_db(KeyValue** head) {
    FILE* file = fopen(DATABASE_FILE, "r");                 // 데이터베이스 파일 열기
    if (!file) return -1;                                   // 파일 열기 실패 시 -1 반환

    int key;
    char value[MAX_VALUE];
    while (fscanf(file, "%d,%99s\n", &key, value) != EOF) { // 파일에서 키 값과 값을 읽어와 리스트에 삽입
        insert_kv(head, key, value);
    }
    fclose(file);                                           // 파일 닫기
    return 0;                                               // 로드 성공 시 0 반환
}

// 데이터베이스를 파일에 저장하는 함수
int save_db(KeyValue* head) {
    FILE* file = fopen(DATABASE_FILE, "w");                 // 데이터베이스 파일 쓰기 모드로 열기
    if (!file) return -1;                                   // 파일 열기 실패 시 -1 반환

    while (head) {                                          // 리스트의 모든 요소를 순회하며
        fprintf(file, "%d,%s\n", head->key, head->value);   // 키 값과 값을 파일에 저장
        head = head->next;                                  // 다음 요소로 이동
    }
    fclose(file);                                           // 파일 닫기
    return 0;                                               // 저장 성공 시 0 반환
}

kv_store.h

#ifndef KV_STORE_H
#define KV_STORE_H

#define DATABASE_FILE "database.txt" // 데이터베이스 파일 이름
#define MAX_KEY 100                  // 최대 키 값
#define MAX_VALUE 100                // 최대 값 길이

// Key-Value 쌍을 저장하는 구조체
typedef struct KeyValue {
    int key;                         // 키 값
    char value[MAX_VALUE];           // 값
    struct KeyValue* next;           // 다음 Key-Value 쌍을 가리키는 포인터
} KeyValue;

// 함수 선언
KeyValue* create_kv(int key, const char* value);   // Key-Value 쌍 생성
void free_kv(KeyValue* head);                      // Key-Value 쌍 메모리 해제
int insert_kv(KeyValue** head, int key, const char* value); // Key-Value 쌍 삽입
char* get_kv(KeyValue* head, int key);             // 키 값에 해당하는 값 검색
int delete_kv(KeyValue** head, int key);           // Key-Value 쌍 삭제
void clear_db(KeyValue** head);                    // 데이터베이스 초기화
void print_all_kv(KeyValue* head);                 // 모든 Key-Value 쌍 출력
int load_db(KeyValue** head);                      // 데이터베이스 로드
int save_db(KeyValue* head);                       // 데이터베이스 저장

#endif
## 컴파일러 설정
CC = gcc                            ## 사용할 컴파일러
CFLAGS = -Wall -Wextra -std=c99     ## 컴파일 옵션: 모든 경고 표시, C99 표준 사용

## 소스 파일 및 오브젝트 파일 설정
SRC = src/main.c src/kv_store.c     ## 소스 파일 목록
OBJ = $(SRC:.c=.o)                  ## 오브젝트 파일 목록 (.c를 .o로 대체)

## 타겟 설정
TARGET = kv_store                   ## 생성할 실행 파일 이름

## 기본 타겟
all: $(TARGET)

## 실행 파일 생성 규칙
$(TARGET): $(OBJ)
	$(CC) $(CFLAGS) -o $@ $^

## 오브젝트 파일 생성 규칙
%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

## clean 타겟: 생성된 파일들 삭제
clean:
	rm -f $(OBJ) $(TARGET)

## .PHONY 타겟: 실제 파일이 아닌 타겟 설정
.PHONY: all clean

3. 빌드 및 실행#

## 프로젝트 디렉토리 이동
cd /home/qhddybuntu/workspace/projects/os-2024/book/projects/202221014/kv_store

## Makefile를 사용하여 프로젝트 빌드
make

## 프로그램 실행
./kv_store

4. 실행 예제#

4.1 Key-Value 쌍 삽입

cv p,1,Gyubeom
cv p,8,Apple
cv p,50,Melon

4.2 키 값 검색

cv g,1
1,Gyubeom
cv g,8
8,Apple
cv g,50
50,Melon

4.3 Key-Value 쌍 삭제

cv d,8
cv g,8
K not found (K=8)

4.4 모든 Key-Value 쌍 출력

cv a
1,Gyubeom
50,Melon

4.5 추가 Key-Value 쌍 삽입 및 출력

cv p,2,Banana
cv p,3,Grape
cv a
1,Gyubeom
2,Banana
3,Grape
50,Melon

4.6 데이터베이스 지우기 및 확인

cv c
database is empty
cv a
database is empty

4.7 프로그램 종료

exit

전체 예제 세션#

cv> cv p,1,Gyubeom
cv> cv p,8,Aplle
cv> cv p,50,Melon
cv> cv g,1
10,Gyubeom
cv> cv g,8
8,Aplle
cv> cv g,30
30,Incheon
cv> cv d,20
cv> cv g,20
K not found (K=20)
cv> cv a
10,Seoul
30,Incheon
cv> cv c
database is empty
cv> cv a
database is empty
cv> cv p,40,Gwangju
cv> cv a
40,Gwangju
cv> exit