프로그래밍/C*

C에서 메모리의 동적 할당

마이바스켓토시아와세바타도로보 2022. 3. 1. 06:50

C 공부는 오래전부터 윤성우의 열혈강의 C로 하고 있다.

물론 이외에도 책은 많이 있으나 이게 가장 쉽고 빠르게 공부가 가능하기도 하고

프로그래밍을 처음으로 공부하게 된 책이므로 아무래도 애정이 간다.

최근에 미뤄뒀던 후반부 몇 챕터를 공부하면서 문제풀이도 해보고 노트정리 하는 셈 치면서 

블로그를 이용해보려 한다.

 

 

-----------------------------------------------------------------------------------

 

 

프로그램 실행 시 운영체제에서 마련하는 네 가지 영역이 있다.

 

 

1. 코드 영역

2. 데이터 영역

3. 힙 영역

4. 스택 영역

 

 

CPU, 메모리나 레지스트리들의 작동원리나 어셈블리에 관해 조금 공부를 했다면

어느 정도 알고 있을 내용이라 생각한다.

 

 

1. 코드 영역 

코드 영역은 쉽게 말해 우리가 작성한 코드들이 저장되는 메모리이다.

 

2. 데이터 영역

전역 변수나, static 변수 등 프로그램 실행 종료까지 유지되는 메모리 공간이다.

 

3. 힙 영역

동적인 활용이 가능한 메모리 영역이다. 프로그래머가 동적인 할당을 위해 이 공간을 사용한다.

 

4. 스택 영역

지역변수와 매개변수들이 할당되는 영역이고 이는 함수나 루프등 현재 진행중인 과정에서만 유효하며 해당 과정이 끝나면 소멸된다.

 

 

-----------------------

 

동적 할당

 

위의 설명대로 기본적인 메모리 규약이 정해져 있다.

다만 프로그램이 실행되는 도중에 따로 메모리 공간을 할당하고 싶거나,

얼만큼의 공간이 필요할지 모르겠는 경우에는 힙 영역에 메모리를 동적 할당하는 것이 좋다.

 

할당 방법

 

#include <stdlib.h> 을 하면 사용할 수 있는 함수들 중,

 

malloc과 free함수를 이용한다.

 

void* ptr = malloc( 할당할 바이트 크기, ex. int = sizeof(int) );

/*
.
.
.
*/

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

 

malloc 함수로 할당하고 싶은 바이트 크기를 매개인자로 넣어 주면 되며,

사용이 끝나면 free 함수로 풀어주면 된다.

 

참고로 malloc 함수의 반환형은 void* , 즉 보이드 포인터의 주소값이다.

어떤 자료형으로 사용할지 정해지지 않았으므로 이 또한 사용자가 알아서 설정해주면 된다.

 

int * ptr = (int *)malloc(sizeof(int));

 

인트형 변수를 할당해주고 싶다면 위와같은 식으로 작성하고,

인트형 배열의 경우 malloc(sizeof(int) * 배열 원소 수)

이런 식으로 작성한다. 다른 자료형의 경우도 비슷하게

해당 자료형이 차지하는 바이트 크기를 malloc 함수의 인자로 전달해주면 된다.

 

----------------------------------------------------------

 

 

calloc 함수

 

malloc과 비슷한 calloc 함수가 있다.

둘의 차이점은, 인자의 전달 방식에 있다.

 

void * calloc(할당할 블록 갯수, 할당할 블록 각각의 크기);

 

물론 calloc 함수 또한 사용 이후 free() 함수로 할당을 해제해줘야 한다.

굳이 해제할 필요는 없다고 하지만

우리가 앞으로 어떤 시스템과 환경에서 어떤 코드를 작성하게 될 지 모르기 때문에

항상 정석적인 방법으로 사용하는 것을 지향하는게 좋다고 생각한다.

 

realloc 함수

 

//힙 영역의 메모리 공간에만 사용 가능
void * realloc(확장하고자 하는 메모리의 주소, 원하는 크기(메모리의 전체 크기));

 

위의 동적 할당 함수들을 유지보수하기 위한 함수이다. 

메모리 공간을 조정하기 위한 함수.

 

realloc 시 기존의 메모리 뒤로 확장하고자 하는 영역이 충분히 확보된 경우 사용 이전 주소 값과 같은 값으로 반환되고

뒤에 영역이 충분하지 않을 시 새로운 적당한 공간을 할당해서 그 주소 값을 반환한다. (저장된 내용엔 변화 없다)

 

무슨 뜻이냐면 더 높은 평수의 집에서 살고 싶은데,

발코니 등 확장이 가능하면 그냥 살던 집에서 살고

집 안이나 밖으로 확장할만한 여유공간이 없으면 다른 더 큰 집으로 이사간다고 생각하면 되겠다.

 

 

참고로 위에 적힌 모든 함수들은 반환값으로

성공 시 메모리의 주소값, 실패 시 NULL을 반환한다.

이를 디버그나 오류 발생 시 참고용으로 잘 이용할 수 있을 것이다 생각한다.

 

 

----------------------------------------------------------------

 

25-2 문제 풀이

개인적으로 프로그래밍 문제 풀이에는 정답이 없다고 생각한다

효율의 문제는 있겠지만 어떻게는 원하는 목적대로 돌아가기만 한다면 ok가 아닐까..

그런 마음가짐으로 문제를 해결하려고 한다.

 

 

> 문제1.

 

먼저 문자열을 입력받아야 한다.

char 형 배열이 유효해 보인다. 그 크기는 가장 먼저 입력받은 정보만큼.

다만 입력받은 문자열이 역순으로 출력되어야 한다.

 

바로 생각나는 방법은 크게 두가지이다.

char 형 배열을 같은 크기로 동시에 2개를 할당한 뒤,

하나는 정순으로 입력받고, 나머지 하나에 정순의 문자열을 역순으로 바꿔서 저장하는 방법 하나와

 

처음 입력받는 문자열 크기정보를 바탕으로 

아예 입력받는 문자열을 처음부터 역순으로 저장하는 방법인데 후자의 방법이 덜 귀찮아 보인다.

 

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

int main(void)
{
	int size;
	char * str;

	printf("input string size : ");
	scanf("%d", &size);

	//배열의 마지막에 널문자를 추가할 것이므로 배열의 크기를 한글자만큼 더 크게 잡는다
	size++;

	str = (char*)malloc(sizeof(char)*size);

	printf("input string : ");
	
    //입력버퍼를 지워준다(*설명 참조)
    getchar();
    
    //배열의 마지막에 널문자를 삽입한 뒤 원하는 크기만큼 입력받는다.
	str[size-1] = '\0';
	size--;

	//진행 상황을 보기 위해 printf 함수를 하나 넣어놨다.
	for(; size != 0; size--)
	{
		str[size-1] = getchar();
		printf("str %d = %c\n", size-1, str[size-1]);
	}
    
	printf("output string is \" %s \"\n", str);

	return 0;
}

 

코드블럭의 들여쓰기가 뭔가 고장이 나버렸지만 사소한 일이므로 넘어가겠다.

이 문제를 풀면서 내가 생각보다 입출력에 대해 잘 알지 못하고 있다고 느껴졌다.

 

*)중간에 getchar(); 를 이용해 버퍼를 하나 비워줬는데,

처음엔 그냥 시도하다가 입력값이 이상하게 들어가길래 해당하는 버퍼를 하나 비워줬다.

예전에 어렴풋이 printf나 scanf 때문이었나 (잘 기억은 안나지만) getchar 전에 버퍼가 하나

들어가 있는 상태라서(개행문자같은게) 한 번 날려줘야 했었던 기억이 나서 비워줬다.

요약하자면 아직 비워줘야 하는 원리나 명확한 이유를 모른다.. 그냥 오류가 나면 이건가? 싶어서 해결하는 수준.

 

*내용추가))

scanf()로 입력을 받으면 \n 문자가 하나 버퍼에 남아있게 된다.따라서 getchar()로 그 문자를 없애주는 과정이 필요.

 

그리고 또 하나 걸리는 부분이 getchar()는 int 형의 반환을 하는 걸로 알았는데

위에서와 같이 char 형으로 입력 받아서 문자열 입력을 받는 것이 

전혀 단 하나의 문제의 소지가 없는지 확실치가 않다.

 

getchar() 로 문자 하나씩 입력 받을 때는 원래 int 형으로 받는 것이라 하는데

위와 같이 사용이 되는 이유가 내가 잘못 쓰고 있는데 컴파일러가 대충 알아듣고 알아서 컴파일 해 주는건지

아니면 그냥 그렇게 써도 되는건지 확실치가 않다.

그래도 문제없이 원하는 결과대로 입출력이 되긴 하지만 좀 찝찝하긴 하다.

 

나중에 입출력에 대해 다시 더 깊이 공부하게 되어서

이건 이런 원리로 이렇게 되는것이고 이렇게 하는 것이 맞고 저렇게 하는 것은 좋지 않다,

등의 판단이 확실하게 서게 되면 다시 한 번 훑어 보기로 하겠다.

 

 

>문제 2

 

정수를 하나 입력 받는다.

-1 입력이 되면 루프를 끝낸다.

이후에 입력받은 정수를 모두 출력한다.

 

조건

-힙 영역에 크기가 5인 배열을 먼저 할당한다

-입력받는 정수가 많아짐에 따라 배열의 길이를 3씩 늘린다

 

 

가장 먼저 떠오르는 해결 방법은

반복해서 입력을 받는데, 첫 사이클은 5만큼, 그 다음부터는 3만큼의 사이클로

메모리를 늘리는 작업을 하는 것이 편해 보인다.

 

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

int main(void)
{
	//첫 사이클은 5로 시작한다
	int cyc = 5;
    //카운트는 입력 시 마다 올라간다
	int count = 0;
	int * arr = (int*)malloc(sizeof(int)*cyc);

	while(1)
	{
		printf("input an integer, -1 to quit : ");
		scanf("%d", &arr[count]);

		printf("%d \n", arr[count]);
        
		//-1인경우 루프를 탈출한다, 또한 매 루프마다 카운트의 값을 항상 1씩 올린다
        if(arr[count++] == -1)
			break;
		
        //첫 사이클이 끝나면 (cyc-1 만큼 반복되면) cyc에 3을 더하고 다시 반복한다. 매 번 메모리 재할당도 해줌.
		if(count == cyc-1)
		{
			cyc += 3;
			arr = realloc(arr, sizeof(int)*cyc);
		}
	}
	
    //입력받은 정수의 수 만큼 재출력한다.
    for(int i = 0; i < count; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

 

대충 어떤 방식으로 짤지만 정해두고 

코드를 짜면서 수정하고 구조를 바꾸고.. 하다보니 완성이 되었다.

간단한 문제니 딱히 추가로 설명할만한 부분이 없는 듯.

 

오늘은 여기까지.