티스토리 뷰

주의 사항!

  • 이 일지는 작성하고 있는 현시점, 공부와 병행하면서 작성되고 있습니다.
  • 공부 중에 떠오른 생각이나 그때그때의 개념정리 같은 내용이 포함됩니다.
  • 따라서 이 일지의 내용은 제가 공부하고 이해한 대로 작성되기 때문에 실제 사실과는 다를 수 있습니다

이번 시간은 '동적 할당 저장 공간의 활용'에 관한 내용입니다.

 

동적 할당을 잘 사용하면 데이터 맞춤 프로그래밍이 가능해집니다.

문자열을 저장하기 위해 선언했던 char 배열을 예로 들어보겠습니다.

우리는 문자열을 저장하기 위해 지금까지는 다음과 같이 배열을 선언했습니다.

char letters[80];

위와 같이 선언된 배열에는 영문79자를 저장할 수 있습니다.

하지만 여기 입력하는 단어가 'bus'라면 나머지 할당된 공간이 너무나도 아깝습니다.

하지만 동적 할당을 사용하면 이와 같이 메모리가 낭비되는 일을 막을 수 있습니다.

 

따라서 동적으로 메모리 공간을 할당하고 이를 효율적으로 활용하는 방법을 배우는 것은 매우 유익합니다.

 

간단한 예제를 통해 살펴보겠습니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main(void)
{
	char* str[3];

	for (int i = 0; i < 3; i++)
	{
		char temp[80];
		printf("문자열을 입력하세요 : ");
		fgets(temp, 80, stdin);
		temp[strlen(temp) - 1] = '\0';

		str[i] = (char*)malloc(sizeof(char) * (strlen(temp) + 1));
		if (str[i] == NULL)
		{
			printf("메모리 공간이 부족합니다.\n");
			exit(1);
		}
        
		strcpy(str[i], temp);
	}

	for (int i = 0; i < 3; i++)
	{
		printf("%s\n", str[i]);
	}

	for (int i = 0; i < 3; i++)
	{
		free(str[i]);
	}
}
/*
실행결과

문자열을 입력하세요 : hello
문자열을 입력하세요 : world!!
문자열을 입력하세요 : my name is koey!
hello
world!!
my name is koey!
*/

문자열의 주소를 저장할 포인터를 먼저 선언했습니다.

이후 for 반복문을 통해 3번 문자열을 입력받습니다.

임시로 문자열을 저장할 temp를 반복문 안에서 선언했습니다.

 

fgets 함수는 문자열의 크기를 비교하면서 입력을 받습니다. 이 함수는 엔터까지 문자열에 포함하기 때문에

temp의 마지막 문자에는 엔터가 들어가 있고, 이를 널문자로 바꿔주었습니다.

 

temp에서 문자열의 수를 구하고 malloc을 호출해 필요한 만큼의 메모리 공간을 할당하는데

temp에 입력된 문자의 수보다 1을 더해서 공간을 할당하고 있습니다.

여기서 1을 더하는 이유는 널문자를 포함하기 위함입니다.

strlen() 함수는 문자의 수를 구하는데 문자열의 가장 마지막에 있는 널문자는 개수에 포함하지 않기 때문에

1을 더해주어서 널문자를 저장할 공간까지 할당해주어야 합니다.

 

이후 할당된 공간에 strcpy()를 이용해 임시로 저장된 temp의 문자열을 모두 복사해줍니다.


동적 할당한 저장 공간을 함수로 처리할 때는 할당한 공간의 구조를 잘 살펴야 합니다. 

 

예로 위의 예제에서, 입력한 문자열을 출력하는 부분을 함수로 만들고,

함수에 매개변수로 메모리 주소를 주어 출력을 하게 구현하려면 어떻게 될까요?

 

예제로 확인해보겠습니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void printStr(char** str);

void main(void)
{
	char* str[21] = { 0 };
	int count = 0;

	for (int i = 0; i < 21; i++)
	{
		char temp[80] = { '\0' };

		printf("문자열을 입력하세요 : ");
		fgets(temp, 80, stdin);
		temp[strlen(temp) - 1] = '\0';

		if (strcmp(temp, "end") == 0) break;
		
		str[i] = (char*)malloc(sizeof(char) * (strlen(temp) + 1));
		strcpy(str[i], temp);
		count++;
	}

	printStr(str);

	for (int i = 0; i < count; i++)
	{
		free(str[i]);
	}
}

void printStr(char** str)
{
	while (*str != NULL)
	{
		printf("%s\n", *str);
		str++;
	}
}
/*
실행결과

문자열을 입력하세요 : hello
문자열을 입력하세요 : world
문자열을 입력하세요 : my
문자열을 입력하세요 : name is
문자열을 입력하세요 : KOEY!!
문자열을 입력하세요 : end
hello
world
my
name is
KOEY!!
*/

우선 앞선 예제와 마찬가지로 문자열의 주소를 저장할 포인터 배열을 선언하고 데이터는 모두 0으로 초기화했습니다.

이 0은 NULL과 같은 의미를 가집니다.

 

count는 입력된 문자열의 개수를 저장하는 변수입니다.

 

for 반복문에서 임시로 문자열을 저장할 temp를 선언합니다.

만약 입력된 문자열이 "end"라면 입력과정을 생략합니다.

임시 저장된 문자열을 strcpy를 이용해 str 포인터로 복사해줍니다.

 

입력된 문자열을 모두 출력하는 기능을 하는 함수 printStr 을 만들었습니다.

이 함수에는 인수로 str을 줄 것입니다. str은 포인터 배열의 첫 요소의 주소입니다.

포인터의 주소이기 때문에 매개변수의 형식은 2중 포인터가 됩니다. 

 

printStr 함수 안에서는 인수로 받은 주소를 간접참조합니다.

str 포인터의 주소를 인수로 받았기 때문에 이를 간접참조하면 해당 주소의 포인터가 저장하고 있는 데이터

즉, 해당 포인터는 문자열의 주소를 데이터로 가지고 있으므로 이 주소를 알 수 있게 됩니다.

 

처음 str 포인터 배열을 선언할 때 0으로 초기화했기 때문에 문자열을 저장하지 않는 포인터는 모두 NULL값을 가집니다.

str을 간접참조하여 NULL값이 아니라면 문자열을 저장하고 있는 포인터라는 소리이므로 해당 문자열을 출력합니다.


지금까지 문자열을 입력하면 문자열의 크기에 맞춰서 메모리 공간을 동적 할당하는 방법들을 배웠습니다.

하지만 이런 방법들도 한계가 있습니다.

바로 초기에 선언한 포인터의 개수만큼만 문자열을 입력받을 수 있다는 한계가 있습니다.

하지만 이는 간단한 응용으로 해결할 수 있습니다.

 

바로 realloc 함수를 이용해 포인터 배열의 크기를 늘려가는 것이 하나의 방법입니다.

 

예제를 통해 살펴보겠습니다.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void printStr(char** ppStr);

void main(void)
{
	int max;

	printf("입력할 문자열의 최대 개수를 입력 : ");
	scanf("%d", &max);
	getchar();

	char** str;

	str = (char**)malloc(sizeof(char*) * (max + 1));
	if (str == NULL)
	{
		printf("메모리가 부족합니다.\n");
		exit(1);
	}

	int i = 0;
	while (1)
	{
		char temp[40];

		printf("문자열을 입력하세요 : ");
		fgets(temp, 40, stdin);
		temp[strlen(temp) - 1] = '\0';
		
		if (temp[0] == '\0') break;
		

		str[i] = (char*)malloc(strlen(temp) + 1);
		if (str[i] == NULL)
		{
			printf("메모리가 부족합니다.\n");
			exit(1);
		}
		strcpy(str[i], temp);
		i++;

		
		if (i == max)
		{
			printf("문자열 입력이 최댓값을 모두 채웠습니다.\n");
			max += 3;
			str = (char**)realloc(str, sizeof(char*) * (max + 1));
			if (str == NULL)
			{
				printf("메모리가 부족합니다.\n");
				exit(1);
			}
		}
	}

	str[i] = 0;
	printStr(str);
}

void printStr(char** ppStr)
{
	while (*ppStr != NULL)
	{
		printf("%s\n", *ppStr);
		ppStr++;
	}
}

/*
실행결과

입력할 문자열의 최대 개수를 입력 : 2
문자열을 입력하세요 : hello
문자열을 입력하세요 : world
문자열 입력이 최댓값을 모두 채웠습니다.
문자열을 입력하세요 : my
문자열을 입력하세요 : name is
문자열을 입력하세요 : koey
문자열 입력이 최댓값을 모두 채웠습니다.
문자열을 입력하세요 : thank you!
문자열을 입력하세요 : 
hello
world
my
name is
koey
thank you!
*/

2중 포인터로 str을 선언했습니다. 

이 2중 포인터는 차후 char* 포인터 배열의 시작주소를 받습니다.

그리고 char* 포인터 배열은 각각 문자열을 저장하기 위해 동적으로 할당되는 공간의 시작주소를 받습니다.

우리는 이 char* 포인터 배열의 크기를 점차 늘려가면서 문자열의 입력을 당초 생각했던 것보다 많이 받을 수 있습니다.

 

max 는 처음 제한할 문자열의 개수입니다. 2로 시작하면 우리는 문자열을 2개까지만 받을 수 있습니다.

이후 char** 포인터인 str에 char*를 요소로 가지는 배열 공간을 만들어 줍니다.

 

반복문 안에서 임시로 문자열을 저장할 char 배열 temp를 선언합니다.

아무것도 입력하지 않고 바로 엔터를 누르면 입력과정을 생략합니다.

temp에 입력받은 문자열을 2중 포인터 str의 부분요소인 char* 에 복사해줍니다.

 

만약 문자열을 입력한 개수가 max값과 같아지면 max를 3늘리고, 그만큼 char* 배열의 크기도 늘리고, 그 배열의 시작주소를 2중포인터 str에 반환합니다.

 

다음 시간엔 지금까지 배운 것을 바탕으로 문제 3개를 풀어보겠습니다.

그리고 그 다음 '사용자 정의 자료형'에 대해 배우겠습니다.

읽어주셔서 감사합니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함