본문 바로가기
프로그래밍/C

[ C언어 ] 프로젝트(삽질일기) : 도서 관리 프로그램 - (3) - 동적 메모리 할당 - 구조체 배열 크기 동적 조절 ( 확대 / 축소 ), #ifdef DEBUG 사용

by eteo 2022. 4. 22.

동적 메모리 할당을 적용한 도서 관리 프로그램 개선 3탄.

 

2022.04.17 - [Language/C] - [C언어] 프로젝트(삽질일기) : 도서 관리 프로그램 - (2) - 동적 메모리 할당

 

이전 까지는 #define을 통해 구조체 배열인 booklist의 SIZE를 20으로 고정했었다.

 

하지만 동적 메모리 할당을 한번 해보니 더 본격적으로 써보고 싶어진다. 도서가 계속 추가 되어 배열이 꽉 찬다면 배열의 크기를 늘리고 싶고 데이터가 줄어들어 빈공간이 널널한다면 배열을 다시 축소하고 싶은데 어떻게 하면 될까?

 

참고로 배열을 선언할 땐 대괄호 안에 변수를 넣을 수 없다. 선언한 뒤 사용할 때는 인덱스 자리에 변수를 넣어서 활용 할 수 있지만 선언 할때 변수를 넣으면는 위와 같이 C2466 에러가 난다. (gcc 컴파일러에선 가능하긴 함)

 

어차피 이후에 메모리할당 함수로 배열의 사이즈를 늘이고, 줄이고 조정해주려면 처음부터 포인터와 malloc을 이용해서 동적으로 할당 받은 후 배열처럼 사용하면 되겠다.

 

그리고 무한루프 안에서 데이터 개수를 계속 체크하고 공간이 부족하거나 남아돌 때 확장/축소한 사이즈의 메모리를 재할당해서 옮겨가면 되지 않을까

 

일단 결과물

 

 

 

main.c

#include <stdio.h>
#include <Windows.h>
#include <stdlib.h>
#include <string.h>
#include "book.h"
#define DEBUG

int listSize=10;

int main(void) {


	// 동적할당. 초기 배열 사이즈 10
	_stBook *booklist = (_stBook*)calloc(listSize, sizeof(_stBook));
	if (booklist == NULL) {
		printf("메모리 할당에 실패하였습니다.");
		return 1;
	}

	// 구조체에 초기값 9개 대입.
	initialData(booklist);

	// 현재 등록된 도서 개수 확인
	char datanum = CountData(booklist);	


	while (1) {	

		if (datanum == listSize) {	// 데이터 꽉찰 시 10단위씩 구조체 배열 확장
			expandM(&booklist);
		}
		else if (datanum < listSize - 20 && listSize >= 30) {	// 빈공간 20개보다 많아지면 10씩 구조체 배열 축소
			cutM(&booklist);
		}

		char menu;
		system("cls");
		menu = DispMenu(datanum, booklist);
		system("cls");
		if (menu == 6) break;
		else if (menu == 1) datanum = AddBook(booklist, datanum);
		else if (menu == 2) datanum = RmBook(booklist, datanum);
		else if (menu == 3) {
			SortMenu(booklist, datanum);
			system("pause");
		}
		else if (menu == 4) {
			SearchBook(booklist, datanum);
			system("pause");
		}
		else if (menu == 5) {
			PrintBook(booklist, datanum);
			system("pause");
		}
#ifdef DEBUG
		else if (menu == 7) {	// 테스트용 도서 추가
			datanum = Testadd(booklist, datanum);
		}
		else if (menu == 8) {	// 테스트용 도서 삭제
			datanum = TestRm(booklist, datanum);
		}
#endif
		else {
			rewind(stdin);
			continue;
		}
	}
	free(booklist);
	return 0;
}
  • malloc 대신 calloc을 사용했다. malloc은 쓰레기 값이 들어있지만 calloc은 할당된 메모리를 0으로 초기화해주기 때문에.
  • 구조체에 초기데이터를 넣어주는 부분은 initialData로 함수화했다. 나중에 파일입출력 기능으로 교체하려고한다.
  • 구조체배열에 데이터가 꽉찰 때마다 사이즈를 10씩 키워 힙메모리를 재할당하는 함수를 호출한다. (ex. 도서 데이터가 9개에서 10개가 되는 순간 배열 크기 20으로 확장)
  • 구조체 배열에 비워져있는 공간이 20개보다 많아지면 다시 사이즈를 10씩 줄여 힙메모리를 재할당하는 함수를 호출한다. (ex. 배열크기가 30이고 도서 데이터를 10개에서 9개가 되는 순간 배열 크기 20으로 축소)
  • 테스트할 때 도서 추가하고 삭제하는데 입력을 여러번해야되서 귀찮기 때문에 디버그용 코드를 추가했다. #ifdef DEBUG, #endif로 가둬뒀기 때문에 나중에 맨위의 #define DEBUG만 지우면 테스트용 기능없이 잘 돌아간다.

 

 

 

참고로 메모리 재할당 함수는 expandM, cutM인데 (&booklist)를 인수로 넘겨준다. booklist가 포인터고 &booklist는 포인터의 주소, 즉 함수안에서는 이중포인터 매개변수로 받는다.

 

일반 변수가 call by value 로 넘겨주면 값이 복사되어 전달된 것이라 원본을 수정할 수 없기 때문에 주소로 넘겨주는 것처럼 포인터도 마찬가지다. 나는 다른 곳에 메모리를 재할당해서 포인터가 그 주소를 가지도록 값을 바꾸고 싶은 거기 때문에 포인터의 주소, 즉 이중포인터를 전달해야한다.

 

 

void expandM(_stBook** list) {	// 메모리 확장, 이중포인터로 받음
	
	listSize += 10;

	_stBook* temp = (_stBook*)calloc(listSize, sizeof(_stBook));	// temp에 확장된 사이즈로 메모리 다시 할당받음
	if (temp != NULL) {	
		_stBook* bfch = *list;	// 예전 주소 임시 포인터에 저장
		memcpy(temp, *list, sizeof(_stBook) * (listSize - 10));	// 예전 주소에서 확장전 사이즈만큼 새 주소에 데이터 복사
		free(bfch);	// 예전 주소 할당 해제
		*list = temp;	// booklist가 새 주소값을 가지도록 대입함
	}
	else {
		printf("메모리 할당에 실패하였습니다.");
		listSize -= 10;
		system("pause");
	}

}

void cutM(_stBook** list) {	// 메모리 축소
	listSize -= 10;

	_stBook* temp = (_stBook*)calloc(listSize, sizeof(_stBook));
	if (temp != NULL) {	// NULL인 경우
		_stBook* bfch = *list;
		memcpy(temp, *list, sizeof(_stBook) * (listSize));
		free(bfch);
		*list = temp;
	}
	else {	// NULL이 아닌 경우
		printf("메모리 할당에 실패하였습니다.");
		listSize += 10;
		system("pause");
	}
}
  • 먼저 전역변수 listSize를 조절한다. 새로운 메모리 할당에 성공하면 예전 주소를 bfch에 잠시 저장해두고 memcpy로 메모리의 값을 복사한다. 예전주소는 free로 할당해제하고, 새로할당받은 주소값을 booklist에 다시 넣는다.

 

 

 

메모리의 확장 축소가 잘되는지 확인하기 위한 코드는 다음과 같다.

printf("(현재 할당된 메모리크기 / 구조체크기 : %2d)\n", _msize(list) / sizeof(_stBook));
  • _msize는 힙에 할당된 메모리크기를 반환하는 함수로 아쉽지만 Visual Studio 전용함수다. 여기에 구조체 하나 사이즈를 나누면 구조체배열의 크기를 알 수 있다.

 

 

참고로 테스트용 도서추가/삭제 기능을 위해 소스파일을 따로 만들었으며 함수는 아래와 같이 생겼다.

test.c

#include <stdio.h>
#include <string.h>
#include "book.h"
#define DEBUG

extern int listSize;

#ifdef DEBUG
char Testadd(_stBook list[], char num);
char TestRm(_stBook list[], char num);

char Testadd(_stBook list[], char num) {	// 테스트용 도서 추가
	int i = num;
	char buf[30];	// 정수->문자열 변환 위한 임시 buffer
	sprintf_s(buf, sizeof(buf), "%d번째 책(test)", i+1);	// 정수를 문자열로 변환해 변수에 저장해주는 함수
	strcpy_s(list[i].title, sizeof(list[i].title), buf);
	strcpy_s(list[i].author, sizeof(list[i].author), "테스트");
	list[i].price = 10000;
	list[i].page = 100;
	list[i].date.year = 1;
	list[i].date.month = 1;
	list[i].date.day = 1;

	i++;

	return i;
}

char TestRm(_stBook list[], char num) {	// 테스트용 도서 삭제
	memset(&list[num-1], 0, sizeof(_stBook));	// 삭제
	num--;	// 데이터 개수 줄임
	return num;
}

#endif
  • 버튼하나로 도서를 추가했을 때 제목이 %d번째 책이었으면 했다. Java였으면 +로 연결해주면 되서 매우 간단했을것 같은데 C언어라서 정수를 문자열로 변환하여 buffer에 저장해주는 함수인 sprintf/sprintf_s를 사용했다. stdio.h 헤더에 포함되어 있다.
  • 함수의 원형 sprintf_s(버퍼의 주소, 버퍼 사이즈, 출력 포맷, 인자)
sprintf_s(char *const _Buffer, const size_t _BufferCount, const char *const _Format, ...)
  • 이후 strcpy로 문자열복사해서 옮긴다.
  • TestRm은 가장 마지막 도서를 삭제하도록 만들었다.

 

 

 

다음으로 추가해보고싶은 것은 파일입출력과 링크드 리스트이다.

자료구조를 배운적 없어 넘 힘들것 같지만 일단 도~전

다음편 :

https://eteo.tistory.com/28

 

[C언어] 프로젝트(삽질일기) : 도서 관리 프로그램 - (4) - 파일 입출력 - fopen/fopen_s, .bin 바이너리

파일 입출력 기능을 추가한 도서 관리 프로그램 개선 4탄. 2022.04.22 - [Language/C] - [C언어] 프로젝트(삽질일기) : 도서 관리 프로그램 - (3) - 동적 메모리 할당 - 구조체 배열 크기 동적 조절 (확대/축

eteo.tistory.com