우당탕탕 개발일지
[C언어] 메모리 (feat. 주소록 프로그램) 본문
리눅스 가상 메모리 구조
가상 메모리는 실제 각 프로세스마다 충분한 메모리를 할당하기에는 메모리 크기의 한계가 있어, 디스크(ROM)의 일부를 확장된 RAM처럼 사용하기 위한 개념이다.
- 실제 물리 메모리가 가지는 공간보다 더 큰 공간으로 확장할 수 있다.
- ram(주기억장치)이나 디스크, 레지스터 등의 공간을 연속적으로 가상 공간에 매핑할 수 있다.
- 메모리 관리에 효율적이다.
가상 메모리의 단위는 페이지 단위로 관리되고, 물리 메모리는 프레임 단위로 관리된다. 페이지와 프레임은 동일한 사이즈를 가진다.

MMU (Memory Management Unit)
MMU(Memory Management Unit)는 가상 메모리를 사용하기 위한 하드웨어 장치로, 가상 주소(프로세스가 참조하는 주소)를 물리 주소(실제 메모리 주소)로 변환해주는 역할을 수행한다. TLB(Translation Lookaside Buffer)는 가상 메모리가 사용하는 페이지 테이블의 캐시를 저장한다.
페이지 테이블 (Paging Table)
물리 메모리의 프레임 번호와 프로세스의 페이지 번호를 엔트리로 가지고 있는 테이블이다. 페이지 테이블을 사용함으로써 프로세스를 연속적으로 처리할 수 있게 된다.
- CPU에서 가상 메모리에 접근하고자 할 때, MMU에서 먼저 TLB라는 고속 보조 장치를 참조한다. TLB에서는 자주 호출되는 주소를 가지고 있다.
- 만약 TLB에 원하는 물리 주소가 있다면, 페이지 테이블에 접근하지 않고, 바로 물리 주소에 접근한다.
- TLB에 원하는 물리주소가 없다면, 페이지 테이블을 참고하여 물리주소에 접근한다.
- 페이지 테이블을 참조할 때, 원하는 페이지가 없는 경우 kernel에서 page fault interrupt가 발생하고 디스크에 원하는 페이지를 ram에 load한다.
- 해당 페이지는 페이지 테이블에 update 된다.

프로세스 메모리 영역

Text(Code) 영역
- 프로그램 명령어를 저장하며, 읽기 전용으로 CPU가 Code 영역에 저장된 명령어들을 하나씩 처리한다.
- 프로그램이 명령어를 변경하지 못하도록 읽기 전용인 경우가 많다.
- Heap이나 Stack에 의하여 메모리 공간이 덮어씌워지지 않도록, 일반적으로 힙 또는 스택 메모리 공간 아래에 위치한다.
- 프로그램이 종료될 때까지 계속 유지된다.
Data 영역
- 전역변수(global), 정적변수(static), 배열(array), 구조체(structure) 등이 저장된다.
- 프로그램이 시작할 때 할당되어 프로그램 종료 시 소멸한다.
Data 영역과 BSS 영역을 구분하는 이유
초기화가 되지 않는 변수는 프로그램이 실행될때 영역만 잡아주면 되고 그 값을 프로그램에 저장하고 있을 필요는 없으나 초기화가 되는 변수는 그 값도 프로그램에 저장하고 있어야 하기 때문에 두가지를 구분해서 영역을 잡는다. 따라서 이러한 bss영역 변수들은 많아져도 프로그램의 실행 코드 사이즈를 늘리지 않는다.
BSS 영역
- 메모리 공간을 효율적으로 사용하기 위해 전역 변수가 위치하는 공간을 지칭하는 이름을 세분화하게 되었다.
- 초기화된 전역 변수가 위치하는 공간을 data 영역이라고 부르고, 초기화되지 않은 전역 변수가 위치하는 공간을 BSS(Block Started Symbol) 영역이라고 부르게 되었다.

- 초기화된 전역 변수들(빨강)은 0x07FF7E93030 대의 메모리를, 초기화되지 않은 전역 변수들(노랑)은 0x07FF7E93036 대의 메모리를 가진다.
- 프로그램이 실행될 땐 code 영역과 data 영역을 올린 뒤, bss 영역 메모리를 추가로 올리기만 하면 공간 확보는 끝난다.
- 초기화되지 않은 변수의 값은 실행 도중 채워질 것이므로 변수의 값을 따로 기억할 필요가 없다는 특징 차이가 있기 때문에 두 종류 전역 변수의 공간을 분리하게 된 것이다.
Stack 영역
- 지역(local) 변수, 매개변수(parameter), 리턴 값 등 잠시 사용되었다가 사라지는 데이터를 저장하는 영역이다.
- 함수 호출 시 할당되고 함수가 끝날 때 소멸한다.
Heap 영역
- 동적 메모리 할당에 사용되는 영역이다. → malloc(), new() 사용
- 메모리 주소 값에 의해서만 참조되고 사용되기에, 프로그램 동작 시(런타임)에 크기가 결정된다.
동적 메모리 할당
동적 할당 메모리는 이름이 없는 변수라고 할 수 있으며, 독점적인 메모리 영역을 차지하고 있어 일단 값을 기억할 수 있지만, 이름이 없으므로 오로지 포인터로만 접근할 수 있다. 그래서 malloc() 함수가 리턴하는 포인터는 반드시 적절한 타입의 포인터 변수로 대입받아야 한다.
더 이상 사용하지 않는 메모리 영역은 반드시 free()를 이용해서 clear 해주어야 한다. 그렇지 않을 경우 메모리 누수가 일어날 수 있다.
✅ malloc()
#include <stdlib.h>
void *malloc(size_t size);
- size 크기 만큼의 메모리를 동적으로 할당한다.
- size가 0이면 malloc()은 NULL 또는 나중에 free()로 해제 가능한 고유한 포인터 값을 반환한다.
✅ 반환값
- 호출 성공 시, 할당된 메모리 주소를 반환한다.
- 호출 실패 시, NULL을 반환한다.
- malloc()은 필요한 메모리를 바이트 단위 하나로만 전달받지만, calloc()은 두 개의 값으로 나누어 전달받는다.
- malloc()은 할당된 메모리에 쓰레기 값이 들어 있지만, calloc()은 메모리 할당 후 모든 메모리를 0으로 채운다.
✅ calloc()
#include <stdlib.h>
void *calloc(size_t nmemb, size_t size);
- nmemb : 할당할 요소의 개수
- size : 요소의 크기
- 요소 개수 x 요소 크기 size 바이트 만큼의 메모리를 할당하고, 모두 0으로 초기화한다.
- nmemb 또는 size가 0이면, NULL 또는 free()로 해제 가능한 고유한 포인터를 반환한다.
- 만약 nmemb × size의 곱셈이 정수 오버플로우를 일으킨다면, 에러를 리턴한다.
✅ 반환값
- 호출 성공 시, 할당된 메모리 주소를 반환한다.
- 호출 실패 시, NULL을 반환한다.
✅ realloc()
이미 할당된 메모리의 크기를 바꾸어 재할당하는 함수
#include <stdlib.h>
void *realloc(void *ptr, size_t size);
- ptr : malloc()이나 calloc()으로 할당한 메모리 포인터
- size : 재할당할 크기
- ptr이 가리키는 메모리 블록의 크기를 size 바이트로 변경한다.
- 만약 ptr == NULL이면, malloc(size)와 같다.
- 만약 size == 0이고 ptr != NULL이면, free(ptr) 와 같다
- ptr은 반드시 malloc(), calloc(), realloc() 중 하나로 반환된 포인터여야 한다.
- 만약 메모리 블록이 다른 위치로 이동된 경우, 기존 메모리는 free(ptr) 처리가 자동으로 된다.
✅ 반환값
- 호출 성공 시, 할당된 메모리 주소를 반환한다.
- 호출 실패 시, NULL을 반환한다.
✅ free()
#include <stdlib.h>
void free(void *ptr);
- ptr이 가리키는 메모리 공간을 해제한다.
- ptr은 malloc(), calloc(), 또는 realloc()으로 할당된 포인터여야 한다.
🔷 주소록 프로그램
- 주소록 프로그램은 다음과 같은 형태로 실행을 한다.
- 프로그램명 [display]
- 프로그램명만 입력된 경우, 사용자로부터 주소록 정보를 받아서 파일에 저장하도록 함
- display가 옵션으로 주어진 경우 주소록 파일 정보를 읽어서 화면에 출력하도록 해야 함
- 프로그램명 [display]
- 주소록 프로그램이 주소록 정보를 저장하는 경우에는 사용자로 부터 주소록 정보를 입력받는 메인 스레드와 파일에 주소록 정보를 쓰는 파일 Write 스레드 2개로 구성되어야 하고 주소록 프그램이 주소록 정보를 출력하는 경우에는 파일에서 읽는 메인 스레드와 화면에 정보를 출력하는 Display 스레드 2개로 구성되어야 한다.
- 두 스레드간에는 Queue 자료 구조를 통해 데이터를 주고 받는다. Queue 자료 구조는 직접 코딩한다.
- Thread간 데이터를 송수신하기 위한 Queue는 세마포어와 mutex lock을 사용하여 구현
🔹 입력
1. 주소록 정보로 입력받는 데이터는 이름, 전화번호, 주소이다.
- 이름은 한글로 최대 4글자
- 전화번호는 xxx-xxxx-xxxx 형태로 입력받는다.
- 주소는 한글로 최대 50자 입력받는다.
2.이름에 exit를 입력할 때까지 주소록 입력을 반복한다.
3.입력한 정보가 잘못된 경우, 에러 문구를 출력하고 이름부터 다시 입력받는다.
🔹 출력
- 주소 정보는 다음과 같이 출력하도록 한다.
——————————————————————————
1 - 김민
——————————————————————————-
전화번호: 010-8821-1234
주소: 서울 서초구 법원로3길 20-7
——————————————————————————-
2 - 이지민
——————————————————————————-
전화번호: 010-8821-1234
주소: 서울 서초구 법원로3길 20-7
——————————————————————————-
전체: 2명
——————————————————————————-
#include <stdio.h>
#include <stdlib.h>
#include<stdint.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include "queue.h"
#include "line.h"
#include "is_korean.h"
#define PRINT_FORMAT "프로그램명 [display]\n"
#define ADDRESS_FILE "address_data"
#define CLOSE_FILE(fp) \
do { \
if ((fp) != NULL) { \
if (fclose(fp) == EOF) { \
printf("[ERROR] fclose: %s\n", strerror(errno)); \
} \
(fp) = NULL; \
} \
} while (0)
int read_file(Queue *queue);
void *write_routine(void *arg);
void *display_routine(void *arg);
int input_address(address_info_t *info);
int check_phone_number(char *number);
int reduce_space(char *address);
int main(int argc, char *argv[]) {
Queue* queue = (Queue*)malloc(sizeof(Queue));
pthread_t p_thread;
init_queue(queue);
if(argc == 1) {
if(pthread_create(&p_thread, NULL, write_routine, (void *)queue) != 0) {
printf("[ERROR] failed pthread created\n");
destroy_queue(queue);
return -1;
}
while(1) {
address_info_t new_info;
if(input_address(&new_info) < 0) {
destroy_queue(queue);
return -1;
}
if(strcmp(new_info.name, "exit") == 0) {
break;
}
if(enqueue(queue, &new_info) < 0) {
destroy_queue(queue);
return -1;
}
}
}
else if(argc == 2) {
if(strcmp(argv[1], "display") != 0) {
printf("[ERROR] format is %s\n", PRINT_FORMAT);
return -1;
}
if(pthread_create(&p_thread, NULL, display_routine, (void *)queue) != 0) {
printf("[ERROR] failed pthread created\n");
destroy_queue(queue);
return -1;
}
if(read_file(queue) < 0) {
destroy_queue(queue);
return -1;
}
}
else {
printf("[ERROR] format is %s\n", PRINT_FORMAT);
return -1;
}
if(pthread_join(p_thread, NULL) < 0) {
printf("[ERROR] pthread_join: %s\n", strerror(errno));
}
if(destroy_queue(queue) < 0) return -1;
return 0;
}
int read_file(Queue *queue) {
FILE *rfp;
if((rfp = fopen(ADDRESS_FILE, "r")) == NULL) {
printf("[ERROR] fopen: %s\n", strerror(errno));
return -1;
}
while(1) {
address_info_t address_info;
int len;
if((len = fread(&address_info, sizeof(address_info_t), 1, rfp)) < 0) {
printf("[ERROR] fread: %s\n", strerror(errno));
CLOSE_FILE(rfp);
return -1;
} else if(len == 0) {
break;
}
if(enqueue(queue, &address_info) < 0) {
printf("[ERROR] failed input queue\n");
CLOSE_FILE(rfp);
return -1;
}
}
CLOSE_FILE(rfp);
return 0;
}
void *write_routine(void *arg) {
if(arg == NULL) {
printf("[ERROR] write_routine: queue is NULL\n");
pthread_exit((void *)(intptr_t) -1);
}
Queue *queue = (Queue *)arg;
FILE *wfp;
if((wfp = fopen(ADDRESS_FILE, "a")) == NULL) {
printf("[ERROR] fopen: %s\n", strerror(errno));
pthread_exit((void *)(intptr_t) -1);
}
while(1) {
address_info_t address_info;
if(dequeue(queue, &address_info) < 0) {
pthread_exit((void *)(intptr_t) -1);
}
printf("[DEBUG] dequeue finish\n");
if(strcmp(address_info.name, "exit") == 0) {
break;
}
if(fwrite(&address_info, sizeof(address_info_t), 1, wfp) != 1) {
printf("[ERROR] %s\n", strerror(errno));
CLOSE_FILE(wfp);
pthread_exit((void *)(intptr_t) -1);
}
}
CLOSE_FILE(wfp);
pthread_exit((void *)(intptr_t) 0);
}
#define LINE "--------------------------------------------------"
void *display_routine(void *arg) {
if(arg == NULL) {
printf("[ERROR] display_routine: queue is NULL\n");
pthread_exit((void *)(intptr_t) -1);
}
Queue *queue = (Queue *)arg;
int count = 0;
while(1) {
bool empty = false;
if(is_empty(queue, &empty) < 0) {
printf("[ERROR] failed check queue is empty\n");
pthread_exit((void *)(intptr_t) -1);
}
if(empty) {
break;
}
count++;
address_info_t address_info;
if(dequeue(queue, &address_info) < 0) {
printf("[ERROR] failed dequeue\n");
pthread_exit((void *)(intptr_t) -1);
}
printf("%s\n%d - %s %s\n전화번호: %s\n주소: %s\n", LINE, count, address_info.name, address_info.phone_number, address_info.address);
}
printf("%s\n전체: %d명\n", LINE, count);
pthread_exit((void *)(intptr_t) 0);
}
#define CHECK_DASH(buf, idx) \
if(buf[idx] != '-') { \
printf("[%d]%c is not '-'\n", (idx)+1, buf[(idx)]) ; \
return -1; \
}
#define CHECK_NUM(buf, idx) \
if ( (buf[(idx)]) < '0' || (buf[(idx)]) > '9' ) { \
printf("[%d]%c is not number\n", (idx)+1, buf[(idx)]) ; \
return -1 ;\
}
int input_address(address_info_t *info) {
while(1) {
printf("Name: ");
char name[MAX_NAME];
if(line(name, sizeof(name)) == -1) {
printf("[ERROR] failed input name\n");
continue;
}
if(strcmp(name, "exit") == 0) {
strcpy(info->name, name);
return 0;
}
if(is_korean(name) < 0) {
printf("[ERROR] failed check if name is korean\n");
continue;
}
printf("Phone Num: ");
char number[MAX_PHONE_NUMBER];
if(line(number, sizeof(number)) == -1) {
printf("[ERROR] failed input phone number\n");
continue;
}
if(check_phone_number(number) < 0) {
printf("[ERROR] failed check phone number format\n");
continue;
}
printf("Address: ");
char address[MAX_ADDRESS];
if(line(address, sizeof(address)) == -1) {
printf("[ERROR] failed input address\n");
continue;
}
if(reduce_space(address) < 0) {
printf("[ERROR] failed reduce address space\n");
continue;
}
strcpy(info->name, name);
strcpy(info->phone_number, number);
strcpy(info->address, address);
return 0;
}
}
int check_phone_number(char *number) {
if(number == NULL) {
printf("[ERROR] phone number must not be NULL\n");
return -1;
}
if(strncmp(number, "010", 3) != 0) {
printf("[ERROR] phone number must start with 010\n");
return -1;
}
int idx = 3;
CHECK_DASH(number, idx);
for(idx += 1; idx < 8; idx++) {
CHECK_NUM(number, idx);
}
CHECK_DASH(number, idx);
for(idx += 1; idx < 13; idx++) {
CHECK_NUM(number, idx);
}
return 0;
}
int reduce_space(char *address) {
if(address == NULL) {
printf("[ERROR] address must not be NULL\n");
return -1;
}
int space = 0, i, idx = 0;
int len = strlen(address);
char temp[len];
for(i = 0; i < len; i++) {
if(address[i] == ' ') {
if(!space) {
temp[idx] = address[i];
space = 1;
}
}
else {
if(space) space = 0;
temp[idx++] = address[i];
}
}
temp[idx] = 0;
strcpy(address, temp);
return 0;
}
[ is_korean.c ]
#include "queue.h"
int is_korean(char *name) {
if(name == NULL) {
printf("이름을 입력해주세요.\n");
return -1;
}
int i = 0, len = 0;
while(name[i] != '\0') {
int first = (unsigned char)name[i];
int second = (unsigned char)name[i + 1];
int third = (unsigned char)name[i + 2];
// UTF-8
if(first >= 0xEA && first <= 0xED) { // ㄱ - ㅎ
if(second < 0x80 || second > 0xBF || third < 0x80 || third > 0xBF) {
printf("이름은 한글만 입력해주세요.\n");
return -1;
}
i += 3;
len++;
if(len >= MAX_NAME) {
printf("이름은 4글자 이내로 입력해주세요.\n");
return -1;
}
} else {
printf("이름은 한글만 입력해주세요.\n");
return -1;
}
}
return 0;
}
[ queue.c ]
#include "queue.h"
#define IS_NULL(QUEUE) \
do { \
if (QUEUE == NULL) { \
printf("[ERROR] queue is NULL\n"); \
return -1; \
} \
} while(0)
#define MUTEX_UNLOCK(MUTEX) \
do { \
if (pthread_mutex_unlock(MUTEX) < 0) { \
printf("[ERROR] mutex unlock: %s\n", strerror(errno)); \
} \
} while(0)
#define SEM_POST(SEM) \
do { \
if (sem_post(SEM) < 0) { \
printf("[ERROR] sem post: %s\n", strerror(errno)); \
} \
} while(0)
int init_queue(Queue *queue) {
IS_NULL(queue);
queue->front = NULL;
queue->rear = NULL;
if(pthread_mutex_init(&queue->mutex, NULL) != 0) {
printf("[ERRPR] mutex_init: %s\n", strerror(errno));
return -1;
}
if(sem_init(&queue->sem_empty, 0, MAX_QUEUE) != 0) {
printf("[ERROR] sem_empty init: %s\n", strerror(errno));
return -1;
}
if(sem_init(&queue->sem_full, 0, 0) != 0) {
printf("[ERROR] sem_full init: %s\n", strerror(errno));
return -1;
}
return 0;
}
int enqueue(Queue *queue, address_info_t *data) { // producer
IS_NULL(queue);
if(data == NULL) {
printf("[ERROR] enqueue: data is NULL\n");
return -1;
}
if(sem_wait(&queue->sem_empty) < 0) {
printf("[ERROR] sem wait: %s\n", strerror(errno));
return -1;
}
if(pthread_mutex_lock(&queue->mutex) < 0) {
printf("[ERROR] mutex lock: %s\n", strerror(errno));
return -1;
}
Node* new_node = (Node*)malloc(sizeof(Node));
if(new_node != NULL) {
new_node->address_info = *data;
new_node->next = NULL;
if(queue->rear == NULL) {
queue->front = new_node;
queue->rear = new_node;
} else {
queue->rear->next = new_node;
queue->rear = new_node;
}
queue->size ++;
} else {
printf("[ERROR] failed create node\n");
MUTEX_UNLOCK(&queue->mutex);
SEM_POST(&queue->sem_empty);
return -1;
}
MUTEX_UNLOCK(&queue->mutex);
SEM_POST(&queue->sem_full);
return 0;
}
int dequeue(Queue *queue, address_info_t *data) { // consumer
IS_NULL(queue);
if(data == NULL) {
printf("[ERROR] dequeue: data is NULL\n");
return -1;
}
if(sem_wait(&queue->sem_full) < 0) {
printf("[ERROR] sem wait: %s\n", strerror(errno));
return -1;
}
if(pthread_mutex_lock(&queue->mutex) != 0) {
printf("[ERROR] mutex lock: %s\n", strerror(errno));
return -1;
}
bool empty;
if(is_empty(queue, &empty) < 0) {
printf("[ERROR] failed check queue is empty\n");
MUTEX_UNLOCK(&queue->mutex);
SEM_POST(&queue->sem_full);
}
if(!empty) {
Node *temp = queue->front;
*data = temp->address_info;
queue->front = temp->next;
free(temp);
} else {
printf("[ERROR] queue is empty\n");
MUTEX_UNLOCK(&queue->mutex);
SEM_POST(&queue->sem_full);
return -1;
}
MUTEX_UNLOCK(&queue->mutex);
SEM_POST(&queue->sem_empty);
return 0;
}
int is_empty(Queue *queue, bool *flag) {
IS_NULL(queue);
if(pthread_mutex_lock(&queue->mutex) < 0) {
printf("[ERROR] mutex lock: %s\n", strerror(errno));
return -1;
}
int val = 0;
if(sem_getvalue(&queue->sem_empty, &val) < 0) {
printf("[ERROR] sem getvalue: %s\n", strerror(errno));
destroy_queue(queue);
return -1;
}
if(val == MAX_QUEUE) {
*flag = true;
} else {
*flag = false;
}
MUTEX_UNLOCK(&queue->mutex);
return 0;
}
int is_full(Queue *queue, bool *flag) {
IS_NULL(queue);
if(pthread_mutex_lock(&queue->mutex) < 0) {
printf("[ERROR] mutex lock: %s\n", strerror(errno));
return -1;
}
int val;
if(sem_getvalue(&queue->sem_empty, &val) < 0) {
printf("[ERROR] sem getvalue: %s\n", strerror(errno));
destroy_queue(queue);
return -1;
}
if(val == 0) {
*flag = true;
} else {
*flag = false;
}
MUTEX_UNLOCK(&queue->mutex);
return 0;
}
int destroy_queue(Queue *queue) {
IS_NULL(queue);
bool empty = false;
while(1) {
if(is_empty(queue, &empty) < 0) {
printf("[ERROR] failed check queue is empty\n");
return -1;
}
if(empty) break;
address_info_t address_info;
if (dequeue(queue, &address_info) < 0) {
printf("[ERROR] failed dequeue\n");
return -1;
}
}
if(pthread_mutex_destroy(&queue->mutex) < 0) {
printf("[ERROR] mutex destroy: %s\n", strerror(errno));
}
if(sem_destroy(&queue->sem_empty) < 0) {
printf("[ERROR] sem destroy: %s\n", strerror(errno));
}
if(sem_destroy(&queue->sem_full) < 0) {
printf("[ERROR] sem destroy: %s\n", strerror(errno));
}
return 0;
}
[ line.c ]
#include <stdio.h>
#include <string.h>
int line(char *buf, int buf_len) {
char ch;
int len = 0;
if(buf == NULL) {
printf("buf is NULL\n");
return -1;
}
if(buf_len < 1) {
puts("buf_len is too small\n");
return -1;
}
while(1) {
ch = getchar();
if(ch == EOF) {
return -1;
}
if(ch == '\n') {
break;
}
if(len < buf_len - 1) {
buf[len++] = ch;
}
}
buf[len] = '\0';
return len;
}'Server > Linux, C' 카테고리의 다른 글
| [C언어] IPC(1) (feat. 공유 메모리) (0) | 2025.10.19 |
|---|---|
| [C언어] 시그널 (feat. Sigaction) (0) | 2025.09.30 |
| [C언어] 동기화 API (feat. Rwlock API 사용해 은행 잔고 프로그램 구현하기) (2) | 2025.09.12 |
| [C언어] 동기화 (feat. Mutex API 사용해 은행 잔고 프로그램 구현하기) (0) | 2025.09.12 |
| [C언어] Thread (4) | 2025.06.25 |