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

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

by eteo 2022. 4. 22.

지난편에 이어 도서 삭제기능을 동적할당을 사용한 버전으로 바꿔보자.

 

2022.04.12 - [Language/C] - [C언어] 프로젝트(삽질일기) : 도서 관리 프로그램 - (1) - 구조체 배열

 

처음으로 돌아가서 삭제기능은 먼저 검색을 수행하고 검색결과를 출력해서 보여준 뒤 삭제할지 말지 선택을 입력받고 삭제하게 해야했다.

 

그럼 삭제함수 안에서 검색함수를 호출해야하고 그 검색결과가 담긴 데이터가 필요한데 여기서 문제가 있다.

 

일반적으로 검색함수 내에서 변수(배열)를 선언하면 그 지역변수는 함수를 빠져나오면서 소멸된다. 하지만 내가 필요한 검색결과가 담긴 변수의 수명은 검색함수의 시작과 끝을 함께하면 안된다.

 

삭제함수가 필요할 때 생성되고 삭제후 필요없어지면 사라져도 되는데 이런상황을 위해 동적할당이 있다. 프로그래머가 원할 때 메모리 공간을 할당하고 또 할당된 공간을 해제하는게 가능토록 해준다.

 

char RmBook(_stBook list[], char num) {
	char rmnum = 0;
	int i = 0;
	char chk = 0;
	printf("------------------------------------------------------------------------------\n");
	printf("   삭제하고자 하는 도서를 검색하세요\n");

// 메모리 동적할당
	char* sr = (char*)calloc(num, sizeof(char));
	if (sr == NULL) {
		printf("메모리 할당에 실패하였습니다.");
		return 1;
	}
// 검색함수 호출
	SearchBookforRm(list, num, sr);

	if (sr[0] == 0) {	// 검색 결과가 없을 때
		printf("\n   메뉴로 돌아갑니다......");
		system("pause");
	}
	else if (sr[1] == 0){	// 검색 결과가 1개일 때
		num = Rm(list, num, sr[0]); // 삭제 함수 호출, 삭제할 번호는 1부터시작하는 번호로 넘겨줌
	}
	else {	// 검색 결과가 여러개 일 때
		printf("\n   삭제할 도서 번호를 입력해주세요 ... [  ]\b\b\b");
		scanf_s("%hhd", &rmnum);
		for (i = 0; i < num; i++) {
			if (sr[i] == rmnum && rmnum!=0) {
				num = Rm(list, num, rmnum);
				chk++;
				break;
			}
		}
		if (chk == 0) { // 검색리스트에 없는 다른 도서번호를 입력했을 때
			printf("\n   잘못 입력하셨습니다...");
			system("pause");
		}
	}

	free(sr);	// 메모리할당 해제

	return num;
}

char Rm(_stBook list[], char num, int listnum) {
	char chk = 0;
	printf("\n   정말 삭제하시겠습니까? Yes [1] / No [0] ... [ ]\b\b");
	scanf_s("%hhd", &chk);
	if (chk == 1) {
		memset(&list[listnum-1], 0, sizeof(_stBook));	// 삭제
		for (int i = listnum-1; i < num; i++) {
			memmove(&list[i], &list[i + 1], sizeof(_stBook));	// 삭제한곳부터 한칸씩 앞으로 땡겨서 정렬
		}
		num--;	// 데이터 개수 줄임
		printf("\n   삭제가 완료되었습니다...");
		system("pause");
		return num;
	}
	else {	// 1이 아닌 다른 숫자를 입력했을 때
		printf("\n   삭제하지 않고 메뉴로 돌아갑니다...");
		system("pause");
		return num;
	}
}

void SearchBookforRm(_stBook list[], char num, char *sr) {
	char keyword[100] = { 0, };
	char chk = 0;

	printf("\n   검색어를 입력하세요.......................[                          ]\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
	scanf_s(" %[^\n]s", keyword, sizeof(keyword));
	printf("\n------------------------------------------------------------------------------\n");
	printf("                제목                 가격      저자     페이지    발행연도   \n");
	printf("==============================================================================\n");
	for (int i = 0; i < num; i++) {
		if (strstr(list[i].title, keyword) != NULL) {	// 키워드로 검색하여 널이 아닌 경우 출력
			PrintSearch(list, i);
			sr[chk] = i + 1;
			chk++;	// 검색결과의 갯수와 열번호 저장
		}
	}
	if (chk == 0) printf("   검색 결과가 없습니다.\n");
	printf("------------------------------------------------------------------------------\n");
}

삭제함수 내에서 char 배열을 현재 도서개수인 num 요소만큼 동적할당하고 검색함수를 호출한 뒤 검색정보를 담아 삭제를 수행하고 나서는 free로 메모리를 해제해준다.

 

 

위에선 메모리 동적할당에 malloc 대신 calloc을 사용했다.

 

malloc과 calloc의 사용법

둘 다 반환형이 void형 포인터이기 때문에 앞쪽에 원하는 자료형 포인터로 형변환 해줘야 하고, 메모리할당에 성공하면 메모리의 주소값을 반환하고 실패한 경우엔 NULL을 반환한다.

 

malloc의 원형 : void* malloc(size_t size)

int *ptr1 = (int*)malloc(sizeof(int));
int *ptr2 = (int*)malloc(sizeof(int)*10);
calloc의 원형 : void* calloc(size_t elt_count, size_t elt_size)

int *ptr1 = (int*)calloc(1, sizeof(int));
int *ptr2 = (int*)calloc(10, sizeof(int));

 

보다시피 malloc은 인수가 1개고, calloc은 인수가 2개다 하지만 둘이 하는 기능은 똑같다.

4*10 즉, 40바이트의 메모리공간을 힙영역에 할당하라고 하는거나 4바이트 블록 10개를 힙영역에 할당하라고 하는거나 그게 그거다.

단, 차이점이 하나 더 있는데 calloc은 할당된 메모리 공간의 값을 모두 0으로 초기화한다. 그게 내가 malloc대신 calloc을 쓴 이유다.

물론 char* sr = (char*)malloc(sizeof(char)*10); 하고 memset(sr, 0, sizeof(char)*10); 해줘도 된다.

 

그 아래에는 메모리할당 실패 시의 오류처리를 해주었다. 안해주면 Visual Studio에선 컴파일러 경고를 낸다.

 

 

그리고 검색함수는 main에서도 호출하고 삭제함수에서도 호출하는데 삭제함수에서는 동적할당한 메모리 주소를 하나 더 인수로 넘겨줘야 한다. 그래서 검색함수를 복붙해서 삭제함수용(SearchBookforRm)으로 하나 더 만들었는데 Java처럼 오버로딩이 됐다면 얼마나 좋을까 하는 생각이 드는 순간이다.

 

할당과 해제를 어디서 해줄 지도 고민했는데 같은 함수 내에서 할당과 해제를 하게끔 하는게 깔끔한 것 같다.

 

또 반복되는 삭제기능을 함수로 따로 뺐고 몇가지 예외처리를 추가했다.

  • 정말 삭제하겠냐는 질문에 1말고 다른 입력을 했을 때의 처리
  • 검색리스트를 보고나서 삭제할 도서번호를 입력하는 때 검색리스트에 없는 번호를 입력하는 경우의 처리

 

뭔가 바꾸고 나니 예전보다 코드가 길어진 느낌도 들지만 BookList가 20개가 아니라 1000개가 되는 상황을 상상해보자.

 

이전처럼 static 배열(변수)를 썼다면 전역변수와 같이 프로그램이 시작할 때 데이터 영역에 올라가서 프로그램이 종료되는 순간까지 공간을 차지하고 있을 것이다. 하지만 이처럼 동적할당을 사용하면 필요한 순간에만 힙영역에 메모리를 차지하므로 훨씬 더 효율적으로 메모리관리를 할 수 있다.

 

 

아래는 디버거로 확인해 본 것

현재 등록된 도서개수가 10개니까, 10바이트 공간이 확보되어 0으로 초기화되었고 "자바"라는 키워드로 검색하니 일치하는 도서 번호인 2, 9가 앞부터 차곡차곡 쌓인다.

free로 메모리 할당 해제 후엔 dd라고 변한다.

 

이는 MS에서 사용하는 매직디버그 값으로

cd : 초기화 되지 않은 할당된 메모리

dd : 할당 후 해제된 메모리

fd : 할당 메모리 양 끝에 넣는 가이드

 

라고함.. 컴파일러 마다 다를테니 여기까지만 알아보자

 

다음에는 삭제기능 뿐만 아니라 프로젝트 전체에 동적메모리 할당을 적용해보도록 하겠다.

 

 

다음편 :

https://eteo.tistory.com/25

 

[C언어] 프로젝트(삽질일기) : 도서 관리 프로그램 - (3) - 동적 메모리 할당 - 구조체 배열 크기 동

동적 메모리 할당을 적용한 도서 관리 프로그램 개선 3탄. 2022.04.17 - [Language/C] - [C언어] 프로젝트(삽질일기) : 도서 관리 프로그램 - (2) - 동적 메모리 할당 이전 까지는 #define을 통해 구조체 배열

eteo.tistory.com