컴퓨터 공부/© C

[C언어] 12. 동적 메모리 할당

letzgorats 2021. 2. 14. 04:27

12. 동적 메모리 할당

 

- 일반적으로 C언어에서 배열의 경우 사전에적절한 크기만큼 할당해주어야 한다.

- “프로그램 실행 도중 언제나우리가 원하는 만큼만 메모리 할당해서 사용하고자 한다면 동적 메모리 할당을 사용한다.

- “동적이라는 말의 의미는 프로그램 실행 도중에라는 의미이다.

 

<동적 메모리 할당 함수>

 

- C언어에서는 malloc()함수를 이용해 원하는 만큼의 메모리 공간을 확보할 수 있다.

- malloc()함수는 메모리 할당에 성공하면 주소를 반환하고, 그렇지 않으면 NULL을 반환한다.

   (그런 후 포인터 변수를 이용해 해당 주소에 접근해 데이터를 처리하는 등의 작업을 수행할 수 있다.)

- malloc()함수는 <stdio.h>라이브러리에 정의되어 있다.

          malloc(할당할 바이트 크기);

 - 동적 메모리 할당을 수행할 때마다 할당되는 포인터의 주소는 변칙적이다.

 

(예제1)

#include <stdio.h>

int main(void) {

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

        printf("%d\n", a);

        *a = malloc(sizeof(int));

        printf("%d\n", a);

        system("pause");
        return 0;
}

(해설) 매번 할당되는 주소값은 프로그램을 실행할 때 마다 달라질 수 있고 컴퓨터마다 다르다. sizeof(int)int형 만큼, 그러니까 4바이트가 반환되는 것인데, malloc(sizeof(int))는 쉽게 표현하면 malloc(4) 랑 같은 뜻이다. 출력을 시켜보면 주소값이 같게 나올 수도 있고 다르게 나올 수도 있다. 프로그램을 게속 새로 돌릴 때 마다 주소값은 우연치 않게 같게 나올 때도 있는 반면, 아예 다르게 나오곤한다. 이는 운영체제가 알아서 그 때 그 때 마다 동적으로 남아있는 공간을 할당해준다는 점에서 변칙적이라고 할 수 있는 것이다.

 

- 동적으로 할당된 변수는 <힙 영역>에 저장된다.

코드 영역

    데이터 영역

      힙 영역

      스택 영역

소스 코드

     전역 변수

     정적 변수

   동적 할당 변수

      지역 변수

      매개 변수

(함수마다포함하고음)

 

- 전통적인 C언어에서느 스택에 선언된 변수는 따로 메모리 해제를 해주지 않아도 된다.

- 반면에 동적으로 할당된 변수는 반드시 free()함수로 메모리 해제를 해주어야 한다.

- 메모리 해제를 하지 않으면 메모리 내의 프로세스 무게가 더해져 언젠가는 오류가 발생한다.

- 메모리 누수 (Memory Leak) 방지는 코어 개발자의 핵심 역량이다.

, malloc()함수와 free()함수는 한 세트이다!

 

(예제2) 할당한 메모리를 해제한 뒤에 다시 할당 받아 사용해 보자.

          이 경우 동일한 메모리 주소를 할당 받을 확률이 높다.

#include <stdio.h>

int main(void) {

        int *a = malloc(sizeof(int));
        printf("%d\n", a);
        free(a);

        a = malloc(sizeof(int));
        printf("%d\n", a);
        free();

        system("pause");
        return 0;
}

(해설) a 주소값을 할당한 이후 출력한 다음에 a를 할당 해제해준다. 그러면 비어있는 a에 다시 새롭게 할당된 메모리 주소를 가리키도록 만들어준 후 다시 출력하고 해제해준다. (malloc()함수의 끝은 항상 free()함수여야하는 걸 잊지마라~!)  그러면 결과는 웬만해서는 둘 다 동일한 주소값이 출력이 된다. 왜 그럴까? 왜냐하면 성공적으로 할당이 해제가 되었기 때문에 이제 맨 처음 할당했던 공간은 다시 비어있는 상태가 된다. 그럼 그 비어있는 주소를 다시 사용하게 될 확률이 높은 것이다. 그렇기 때문에 아까 free()없이 그냥 실행했던 것과는 다르게 거의 100퍼센트에 가까운 확률로 똑같은 주소값을 할당받을 수 있게 되는 것이다.

 결론은 malloc()함수만 계속 쓰다보면 프로세스 무게가 커져서 언젠간 프로그램 오류가 나니까 malloc()함수를 쓸 때면 항상 free()함수로 닫아주는 것을 잊지마라!!!!

 

 

<동적으로 문자열 처리하기>

 

- 일괄적인 범위의 메모리를 모두 특정한 값으로 설정하기 위해서는 memset()을 사용한다.

 

          memset(포인터,,크기);세가지 매개 변수가 들어간다.

 

- 한 바이트 씩 값을 저장하므로 문자열 배열의 처리 방식과 흡사하다.

- 따라서, memset()함수는 <string.h>라이브러리에 선언되어 있다.

 

(예제3) memset()함수를 이용해 특정한 범위의 원소를 모두 다 일괄적으로 처리

#include <stdio.h>

#include <string.h>

int main(void) {

        char* a = malloc(100);

        memset(a,'A',100);

        for (int i=0;i<100;i++) {

                printf("%c",a[i]);

        }

        system("pause");
        return 0;
}

(해설) 가장먼저 <string.h>를 불러와야한다. 그리고 a라는 포인터를 만들어서 총 100바이트만큼 공간을 할당해준다. 그 다음에 memset()함수를 통해 a배열을 전부 A라는 문자로 채우도록 한다. , A의 아스키코드값인 65 100개로 만큼 채운다. 따라서 a라는 문자열 포인터는 전부 A로 채워진다. 기존에는 단순히 반복문을 이용해 원소를 처리했으나 내부적으로 memset()이 좀더 빠르게 동작한다.

 

(예제4) 동적 메모리 할당(지금까지 배운거 다 종합한 예제니까 난이도가 좀 있다!)

#include <stdio.h>

#include <stdlib.h>

int main(void) {

        int ** p = (int**)malloc(sizeof(int*) * 3);

        for (int i = 0; i < 3; i++) {

                *(p + i) = (int*)malloc(sizeof(int) * 3); 

        }

        for (int i = 0; i < 3; i++){

                for (int j = 0; j < 3; j++) {

                        *(*(p + i) + j) = i * 3 + j;

                }

        }

        for (int i = 0; i < 3; i++) {

                for (int j = 0; j < 3; j++) {

                        printf("%d ", *(*(p + i) + j));

                }

        printf("\n");

        }

        system("pause"); 
        return 0;
}

(해설) 맨 처음에 이중포인터로 만들어주고 이제 이중 포인터에 대한 것으로 행을 만들어 줄 건데, 행이 총 3개가 들어가도록 만들고 싶어서 3을 곱해주는 것이다. , 하나의 포인터 같은 경우는 한 개의 1차원 배열을 처리 할 수 있으니까 (sizeof(int*))1차원 배열의 시작점을 가리키는 포인터라고 보면 되고 그게 3개 있는 거니까 총 3개의 행을 가리키는 포인터 **p를 만들었다고 할 수 있는 것이다.

 

이제, i0부터 3까지 1씩 증가하면서 p+i값을 초기화 시켜줄 건데, p+i는 각각의 행을 의미하는 건데 각각의 행은 malloc()함수를 이용해서 세 개 만큼씩 배열 형태로 선언 할 수 있게 해준다.( , 3개의 행이 3개의 열을 각각 가지는 거니까 총 3x3 2차원 배열이라고 이해할 수 있다.)

 

또, 그 2차원 배열의 값을 채워 넣을 건데, 2차원 배열이니까 i j 두개를 이용해서 이중 for문 형태로 접근하면 된다.

*(*(p+i)+j)은 특정 행에서 몇 번째 열인지 j를 통해 알려주고 그 위치에 있는 값을 ix3+j 로 넣어주면 된다. 행은 0,1,2 로 증가하되 j 또한 0,1,2 로 증가하니까 차례대로 0,1,2,3,4,5,6,7,8 이 다 각각의 행렬로 들어가게 될 것이다.

 

이렇게 행렬에 들어간 내용을 출력하기 위해서 printf를 이용해서 각각의 원소를 차례대로 출력하게 만든다. 그러면 0부터 8까지의 숫자가 3x3이라는 2차원 배열 형태로 출력되어 나온다.

이런식으로, 동적메모리 할당을 이용해서 배열을 사용하게 되면 미리 사전에 main함수 밖에서 2차원 배열을 만들어 놓지 않더라도 순식간에 동적으로 정확히 자기가 원하는 크기만큼만 배열을 만들어서 메모리 공간을 효율적으로 사용할 수가 있는 것이다.

일반적으로, 프로그램이 종료되는 순간 프로그램 안에 사용되었던 모든 내용들은 다 메모리 해제가 이루어지니까 굳이 이런 간단한 예제를 다룰 때에는 일일이 굳이 free()함수를 이용하지 않겠지만 실제로 상용프로그램을 개발할 때에는 free()함수를 꼭 넣어서 메모리 해제를 해줘야한다.

 

(정리)

1) 동적 메모리 할당을 이용해 프로그램이 실행 중인 도중에 메모리 공간을 배정 받을 수 있다.

2) 동적으로 할당 받은 프로그램은 반드시 명시적으로 free()함수를 이용해 할당 해제를 해야만 한다.

반응형