동적할당을 사용하는 이유는 무엇일까?

c언어의 메모리 구조에 대해서 알고 있다면 동적할당을 사용하는 이유를 쉽게 알 것이다.


2014/06/26 - [Programming/C언어] - [C] 스택(Stack), 힙(Heap), 데이터(Data)영역


malloc 함수

 - 동적으로 메모리를 할당하는 함수 (힙 영역에 메모리를 할당)


#include <stdlib.h>

void* malloc(size_t size)	// malloc 함수의 원형

함수 호출시 할당하고자 하는 메모리의 크기를 바이트 단위로 전달하면 그 크기만큼 메모리를 할당하게 된다.

그리고 할당한 메모리의 주소(첫 번째 바이트의 주소)를 리턴한다.

메모리 할당에 실패하면 NULL이 리턴된다.


리턴형이 void*(void 포인터) ??


malloc은 단순히 메모리만 할당하는 함수이기 때문에 개발자가 어떠한 데이터 형을 저장하는지 예측할 수 없다.

예를들어 4바이트를 할당하였을 경우 int형 데이터를 저장하기 위해서 사용하는지, float형 데이터를 사용하는지 예측할 수 없기 때문에 void포인터를 반환하여 개발자가 알맞은 용도로 변환하여 사용할 수 있도록 만든것이다.


예를들어 int형 데이터를 저장하기 위해서는 리턴되는 void*을 int*로 변환해야 한다.

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



위의 그림은 포인터 변수 i에 4바이트를 할당하는 그림이다.


1. sizeof(int)의 값은 4이다. 4라는 값을 전달하면서 malloc 함수를 호출한다.

2. 할당된 메모리의 주소가 void*형으로 리턴된다. 리턴되는 void*를 사용하려는 int*형으로 변환한다.

3. 포인터 변수 i에 대입한다.


malloc함수 사용 예

동적 할당을 사용하여 arr_1의 배열의 값을 대입하는 소스를 보며 malloc함수 사용법을 이해해보자.

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

int main() {
	int arr_1[5];	// 배열 선언
	int *arr_2;		// 포인터 변수 선언
	int i;

	for(i = 0; i < 5; i++) {
		arr_1[i] = i+1;	// 배열에 값 대입
	}

	arr_2 = (int*) malloc(sizeof(int)*5);	// 메모리 할당, 배열의 크기만큼 할당하기 위해 5를 곱함

	for(i = 0; i < 5; i++) {
		arr_2[i] = arr_1[i];
		printf("%d ", arr_2[i]);
	}

	return 0;
}

free 함수

 - 힙 영역에 할당된 메모리를 해제하는 함수


메모리를 할당만 하고 해제해 주지 않는다면, 언젠가는 메모리가 부족한 현상이 발생 할 것이다.

할당된 메모리가 더 이상 필요하지 않을경우 free함수를 이용하여 메모리를 해제시켜 줘야한다.


#include <stdlib.h>

void free(void* ptr)	// free 함수의 원형


free함수 사용 예

위의 예제에서 free함수만 추가시켰다.

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

int main() {
	int arr_1[5];	// 배열 선언
	int *arr_2;		// 포인터 변수 선언
	int i;

	for(i = 0; i < 5; i++) {
		arr_1[i] = i+1;	// 배열에 값 대입
	}

	arr_2 = (int*) malloc(sizeof(int)*5);	// 메모리 할당, 배열의 크기만큼 할당하기 위해 5를 곱함

	for(i = 0; i < 5; i++) {
		arr_2[i] = arr_1[i];
		printf("%d ", arr_2[i]);
	}

	free(arr_2);	// free함수를 이용하여 메모리 해제

	return 0;
}

calloc 함수

 - calloc함수는 malloc함수와 같은 기능을 지니고 있다. 다만 사용하는 형태가 조금 다를 뿐이다.


#include <stdlib.h>

void* calloc(size_t elt_count, size_t elt_size)	// calloc 함수 원형

calloc 함수는 elt_size 크기의 변수를 elt_count 개 만큼 저장할 수 있는 메모리 공간을 할당하라는 의미를 갖는다.


calloc함수 사용 예

위의 예제에서 malloc 함수대신 calloc 함수를 사용하였다.

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

int main() {
	int arr_1[5];	// 배열 선언
	int *arr_2;		// 포인터 변수 선언
	int i;

	for(i = 0; i < 5; i++) {
		arr_1[i] = i+1;	// 배열에 값 대입
	}

	//arr_2 = (int*) malloc(sizeof(int)*5);	// 메모리 할당, 배열의 크기만큼 할당하기 위해 5를 곱함
	arr_2 = (int*) calloc(5, sizeof(int));	// sizoe(int)크기의 변수를 5개 저장할 수 있는 공간할당

	for(i = 0; i < 5; i++) {
		arr_2[i] = arr_1[i];
		printf("%d ", arr_2[i]);
	}

	free(arr_2);	// free함수를 이용하여 메모리 해제

	return 0;
}

malloc함수와 calloc함수의 차이점!

malloc은 할당된 공간의 값을은 바꾸지 않는다.

calloc은 할당된 공간의 값을 모두 0으로 바꾼다.

배열을 할당하고 모두 0으로 초기화할 필요가 있을경우에는 calloc을 쓰면 편하다.


realloc 함수

 - 이미 할당한 공간의 크기를 바꿀 때 realloc 함수를 사용한다.


#include <stdlib.h>

void* realloc(void* memblock, size_t size);	// realloc 함수의 원형

이미 할당한 포인터 변수를 memblock에 넣고, 바꾸고 싶은 공간의 크기를 size에 입력하여 사용한다.


realloc함수 사용 예

malloc함수를 사용한 예제에서 realloc 함수를 사용하여 변경하였다.


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

int main() {
	int arr_1[10];	// 배열 선언
	int *arr_2;		// 포인터 변수 선언
	int i;

	for(i = 0; i < 10; i++) {
		arr_1[i] = i+1;	// 배열에 값 대입
	}

	arr_2 = (int*) malloc(sizeof(int)*5);	// 메모리 할당, 배열의 크기만큼 할당하기 위해 5를 곱함

	for(i = 0; i < 5; i++) {
		arr_2[i] = arr_1[i];
		printf("%d ", arr_2[i]);
	}

	printf("\n");

	// sizeof(int) = 4바이트
	realloc(arr_2, sizeof(int)*10);	// arr_2의 메모리를 40바이트로 재 할당
	// arr_2의 메모리 크기 : 20바이트 -> 40바이트

	for(i = 0; i < 10; i++) {
		arr_2[i] = arr_1[i];
		printf("%d ", arr_2[i]);
	}

	free(arr_2);	// free함수를 이용하여 메모리 해제

	return 0;
}



  1. 미소 2015.03.05 22:35 신고

    잘보고갑니다

  2. sms 2015.09.29 16:32 신고

    정리를 정말 깔끔하게 해두셨네요. 잘 보고 갑니다.

  3. 눈작은나 2015.10.03 11:44 신고

    디테일한 부분까지~~ 엔지니어의 숨결이..

  4. HightSchool Strudent 2015.12.23 00:04 신고

    안녕하세요, C언어를 공부하고 있는 고등학생입니다.

    malloc을 통한 동적할당을 쓰고 싶은 곳이 있어 공부하고 있는 중인데,
    자꾸 return이 실행되기 직전에 프로그램이 중지되네요...;;

    짠 코드는,

    int *seque, size;
    scanf("%d", &size);

    seque = (int *)malloc(size);
    int i;
    for(i=0; i<size; i++){
    seque[i]=i;
    }
    for(i=0; i<size; i++){
    printf("%d ", seque[i]);
    }
    printf("\n");
    printf("TheEnd_________________EndLine_________________\n");
    free(seque);

    return 0;
    }

    입니다. 16, 32와 같은 수를 입력해주면 분명히 그 전 수 까지 다 출력하고

    TheEnd_________________EndLine_________________ 도 문제 없이 출력합니다.
    그런데, 그 직후에 프로그램 실행 성공에 대한 정보(IDE는 Dev Cpp를 사용하고 있습니다.)를 띄우지 못하고 중지되었다는 알림이 뜹니다.

    같은 코드를 int형으로 하지 않고, char 형으로 바꾸어 하면 문제가 없이 실행 종료까지 되고요.

    뭐가 문제이고 어떻게 해야하나요?

    • 별거없는대학생 2016.01.11 16:51 신고

      size가 integer형이기 때문에 크기는 4byte입니다.
      동적할당을 seque에 하고 싶다면 seque = (int *)malloc(sizeof(int)*size)라고 하는게 맞는 것 같습니다.

  5. 주로스 2016.01.19 17:31 신고

    그렇군요... calloc( )의 예가 잘 정리되있네여 감사감사

  6. 2016.01.26 01:20

    비밀댓글입니다

  7. ralock 2016.05.04 13:09 신고

    Good!! 정리 잘되있네요

  8. 하영 2016.05.08 18:15 신고

    너무 좋은 글 감사합니다!

  9. internet 2016.06.15 09:04 신고

    동적할당 ㅎㅎ
    잘보고갑니다

  10. 굿굿 2016.07.05 17:56 신고


    리버싱 공부하면서 책이나 인터넷에 그렇다고 하니 그런줄말알고 넘어갔던 내용들인데
    정리가 잘 되어있어 공부하는데 많은 도움이 되었습니다. 감사합니다

  11. 대3 2016.10.12 21:02 신고

    대학교 컴공 3학년인데 먼말인지 모르겠네요 ㅎ

    자살각

    • 이제막신입 2016.10.18 10:14 신고

      허허;;; 컴공 3학년이시라면서;;;
      그럼 안 되는데.

  12. sad 2016.11.24 11:14 신고

    깔끔한 설명 감사합니다

  13. ck10 2017.09.05 22:56 신고

    감사합니다 덕분에 잘배워갑니다!

  14. c+=c++ 2018.06.09 22:37 신고

    와우, 깔끔한 설명 감사합니다.

C언어의 메모리 구조


프로그램을 실행시키면 운영체제는 우리가 실행시킨 프로그램을 위해 메모리 공간을 할당해준다. 

할당되는 메모리 공간은 크게 스택(Stack), 힙(Heap), 데이터(Data)영역으로 나뉘어진다. 

이러한 메모리 공간이 어떠한 용도로 언제, 어디서 할당되는지 알아보도록 하자.


할당 시기 : 프로그램이 실행될 때마다

할당 장소 : 메인 메모리(RAM)

할당 용도 : 프로그램 실행 시 필요한 메모리 공간(지역변수, 전역변수 선언을 위해) 할당




데이터(Data) 영역


 - 전역 변수와 static 변수가 할당되는 영역

 - 프로그램의 시작과 동시에 할당되고, 프로그램이 종료되어야 메모리에서 소멸됨

 

#include <stdio.h>

int a = 10;	// 데이터 영역에 할당
int b = 20;	// 데이터 영역에 할당

int main() {

	...

	return 0;
}

위와 같은 코드에서 int형 변수 a, b는 프로그램 실행시, main 함수가 호출되기 전에 데이터 영역에 할당된다.

그렇기 때문에 프로그램이 종료될 때까지 메모리상에 존재한다.

(전역변수가 프로그램이 종료될 때 까지 존재하는 이유)



스택(Stack) 영역


 - 함수 호출 시 생성되는 지역 변수와 매개 변수가 저장되는 영역

 - 함수 호출이 완료되면 사라짐

 

#include <stdio.h>

void fct1(int);
void fct2(int);

int a = 10;	// 데이터 영역에 할당
int b = 20;	// 데이터 영역에 할당

int main() {

	int i = 100;	// 지역변수 i가 스택 영역에 할당

	fct1(i);
	fct2(i);

	return 0;
}

void fct1(int c) {
	int d = 30;	// 매개변수 c와 지역변수 d가 스택영역에 할당
}

void fct2(int e) {
	int f = 40;	// 매개변수 e와 지역변수 f가 스택영역에 할당
}

main함수fct1, fct2라는 함수를 추가하였다. 

a, b를 데이터 영역에 할당한 뒤에 main함수를 호출하면서 int형 변수 i는 지역변수로서 스택영역에 할당된다.

그 뒤에 fct1()이라는 함수를 호출하면서 fct1함수의 매개변수인 cd가 스택영역에 할당된다.

fct1()이라는 함수호출이 끝나면 cd는 스택영역에서 삭제되며, 

그 뒤 fct2()라는 함수를 호출하면서 매개변수 e와 지역변수 f가 스택영역에 할당된다.

스택영역은 그 이름그대로 스택의 성질을 띄고있다.


 

힙(Heap) 영역


 - 필요에 의해 동적으로 메모리를 할당 할 때 사용


지금까지 데이터영역과 스택영역을 알아보았는데, 저 두가지 영역만 있으면 코드를 문제없이 짤 수 있을것 처럼 보인다.

그럼 힙영역은 왜 필요한 것일까?


힙 영역은 왜 필요할까?

제일 첫번째 그림을 보면 힙 영역은 프로그래머가 할당한다고 되어있다. 

그럼 언제 할당을 할까? 

배열을 예를들어서 설명을 하겠다.


우리는 배열을 선언할때 상수로 선언을 한다.

int main() {

	// 정상적인 배열선언
	int arr[10];

	// 비 정상적인 배열선언
	int i = 0;
	scanf("%d", &i);
	int arr[i];

	return 0;
}

배열의 길이를 사용자가 입력한 숫자로 잡아주는 것은 비 정상적인 배열선언이다. 왜 비 정상적일까?

메모리 구조에 대해서 잘 파악하고 있다면 당연한 이야기다.


제일 첫번째 그림을 다시보자, 스택 영역에 할당될 메모리의 크기는 컴파일 타임(컴파일 하는 동안)에 결정된다고 되어있다.

정상적인 배열 선언의 경우 arr이라는 배열의 크기가 40바이트 라는것을 알 수 있다.

하지만 비 정상적인 배열선언의 경우 i의 크기가 4바이트 라는 것을 알 수 는 있으나, arr이라는 배열의 크기는 알 수 없다.


그렇다면 다음과 같이 배열을 선언할 때는 문제가 없을까?

int main() {
	
	int i = 10;
	int arr[i];

	return 0;
}

i 라는 변수가 10이기 때문에 arr이라는 배열의 크기가 10이라는 것을 알 수 있지 않을까?

결과는 아니다.


컴파일을 하는 동안 i4바이트의 크기라는 것을 알 수는 있으나, 그 값이 10으로 초기화 되었다는 사실은 무시하고 넘어간다. 값이 10으로 초기화 되었다는 사실은 실행되는 동안, 즉 런타임에 결정된다.

그렇기 때문에 컴파일러는 arr의 크기가 40바이트가 된다는 사실을 알 수 없다. 


사용자의 요구에 맞게 메모리를 할당해 주기 위해서는(런타임에 메모리 크기를 결정하고 싶을 때) 메모리 동적 할당을 통해 힙 영역에 메모리를 할당해야 한다.


힙 영역 : 할당해야 할 메모리의 크기를 프로그램이 실행되는 동안 결정해야 하는 경우(런 타임때) 유용하게 사용되는 공간


힙 영역을 사용하기 위해서는 동적할당에 대해서 공부하여야 한다.


2014/06/26 - [Programming/C언어] - [C] malloc, calloc, realloc을 이용한 메모리 동적 할당


  1. 컴돌이 2015.12.09 14:09 신고

    감사합니다!!

  2. Seph 2016.03.26 23:39 신고

    감사합니다 ^^

  3. hamji 2016.04.06 13:58 신고

    프로그램 공부하다 방문하였습니다. 글 잘 읽고 갑니다. ^^

  4. 맹훈 2016.05.01 16:44 신고

    int i=10
    int arr[i]

    이거 오류 발생안하는데요??

    • 11 2016.12.02 11:37 신고

      전역변수로 선언하면 오류 안납니다.

  5. kim 2016.05.27 21:43 신고

    친절한 설명듣고 갑니다~

  6. thanks! 2016.11.10 19:33 신고

    설명 작살나네여.... 고맙습니다

  7. sad 2016.11.24 11:11 신고

    깔끔한 설명감사합니다!

  8. 2017.05.02 23:15

    비밀댓글입니다

  9. 공부하는이 2017.05.02 23:16 신고

    비밀글 남긴거 남긴사람은 어떻게보나요?? ㅜㅜ

    • 공부하는이 2017.05.02 23:24 신고

      메모리가 한번에 잘 이해되는 글이에요
      그림도 그렇고 내용도 그렇고
      머릿속에 확 들어옵니다 ^^
      좋은글 감사합니다

  10. 지나가다가... 2017.05.31 15:49 신고

    뒷북인것 같지만..
    잘못된 정보가 인터넷에 돌고 있어서 알려드립니다.
    변수로 배열 크기를 지정하는 것은 C99부터 가능한 기능입니다.
    비정상적인 배열 선언이 아니며
    scanf 와 같은 함수로 입력받아 배열 크기 지정하는 것도 가능하다는 뜻이죠.
    어떤 기준으로 비정상적이다 라고 말씀하신지 잘 모르겠으나..
    힙영역에 선언되지 않으면서, 컴파일 타임에 배열의 크기를 알 수 없기 때문에 비정상적이다라고 보신다면... 객관적인 정보는 아닌것 같구요,
    위와 같이 가변 할당을 해주어도 사용자의 요구에 맞게 메모리를 할당 해줍니다.
    동적할당과 같이 크기를 하드코딩 하지 않으면서도 sizeof로 할당된 사이즈도 구할 수 있고, (동적할당한 포인터는 전체 사이즈를 구할 수 없기에 따로 관리해야 하죠)
    C에서 사용하는 경우 스택영역이니 메모리릭 신경쓰지 않고 사용할 수 있기 때문에 동적할당 보다 한정된 스코프에서 사용하기에는 용이하다고 봅니다.
    아래 위키 백과에서 가변 길이 배열 관련 내용을 읽어보시기 바랍니다.
    https://en.wikipedia.org/wiki/Variable-length_array

    • 흐미야 2017.08.09 17:32 신고

      지나가다가... 님이 좋은 내용 써주셨네요.
      저도 C는 오래했다고 생각했는데 새로운 내용이네요.
      말씀하신대로면 Heap 영역이 아니라 Stack 영역에 메모리가 잡히고 Life time도 지역 변수랑 동일하겠네요. 그래서 말씀하신대로 Leak에 대해서도 자유로워질 수 있겠네요. 전제 조건은 함수 호출 이전에 명시적으로 기술해야 한다고 했으니, main 함수에서는 못쓰게 되는 거겠군요. 좋은 지적 내용이십니다.
      C가 점점 최신 언어의 장점을 받아들이는 건가요? C만 해온 사람 입장으로서는 발전하는 것 같아서 좋아요.

  11. 장동규 2017.06.15 14:39 신고

    이해되었습니다! 감사합니다!

  12. 아주 지리는 구먼 2017.06.16 18:39 신고

    굿잡

  13. 아주 지리는 구먼 2017.06.16 18:54 신고

    전역변수로 하면 선언 된다고 하면(int a= 5; int b[a];)

    데이터영역(전역,static)은 동적할당 처럼 선언 할 수 있다는 건가?? 근데 결국 동적할당 처럼 사용은 못하겠지만... 선언만 야매로 쓸수있게되네?

    지역변수로 스택영역에서 사용할라면 빌드 자체가 안되는거고.. 런타임 단계에서 스택영역 애들이 아직 할당이안되서 메모리에....맞음?

    결국 그면 런타임할때 이미 데이터 영역(전역, static)들은 모두 할당이 끝난거? 라고 생각하면 됨?

    그래서 되는거고? 하지만 진정한 의미의 동적할당용 변수로는 사용못하고..(구동중에 변경 불가)

    맞음?

  14. 아주 지리는 구먼 2017.06.16 18:56 신고

    근데 위에 지나가다가.. 개소리는뭐임?

C에는 자연로그를 구하는 log() 함수와, 밑(base)이 10인 상용 로그를 구하는 log10()함수만 있기때문에 밑이 2인 로그를 구하려면 직접 함수를 만들어야 한다. 


logB(숫자, 밑)


이라는 함수를 만들어 사용하도록 하자.

double logB(double x, double base) {
	return log(x)/log(base);
}


매크로 함수로 정의해서 사용해도 잘 된다.

#define logB(x, base) log(x)/log(base)


base에 2.0 대신 다른수를 넣어주면 다양한 밑을 가진 로그를 구할 수 있다.


원리는 다음과 같은 공식을 사용한 것이다.




예제 

#include <stdio.h>
#include <math.h>

//#define logB(x, base) log(x)/log(base)

double logB(double x, double base) {
	return log(x)/log(base);
}

int main() {
	printf("log2(8) = %f", logB(8.0,2.0));

	return 0;
}



#include <math.h> 를 사용하여 수식을 계산할때, 원주율값이 필요 한 경우가 있다.


원주율값을 잘 외우고 있는경우 상관은 없지만, 단순히 3.14라는 값을 사용하여 알고리즘 문제를 풀 때 소수점 뒷자리 문제로 답이 틀리는 경우가 종종 발생할 수 있다. 


그렇다고 원주율을 소수점 10자리 이상 외우고 다닐수는 없다.


이럴경우 math.h에서 원주율 값을 제공해주지 않을까 찾아보지만 M_PI를 사용하면 에러가 발생한다.


M_PI 상수는 C표준이 아니기때문에 기본적으로 사용할 수 없다고 한다.


math.h를 include 하기전에


#define _USE_MATH_DEFINES


를 정의해 주어야 한다.



이제 M_PI를 사용할 수 있다.

  1. cppuser 2018.07.13 14:56 신고

    외우는게 편할때가 있습니다.. 3.14159265358979... 단순히 pi하나만 필요할 때... const float으로 정의해 쓰면 편하더라구요. 어차피 리터럴인것..

+ Recent posts