malloc, calloc, realloc 동적 메모리 할당


malloc, calloc, realloc 동적 메모리 할당


이번에는 동적 메모리 할당에 대해서 알 알보자.

동정 메모리 할당 함수에는 malloc(), calloc(), realloc() 함수가 있다

이 함수를 사용하기 위해서는 stdlib.h 파일을 include 해줘야 한다.

일단 malloc() 함수에 대해서 알아보자.


void* malloc(size_t size) 함수의 원형이다. 반환형은 void*(void 포인트형)이다. 결국은 우리가 원하는 자료형으로 할 수 있다.

malloc 함수는 할당된 주소를 반환한다. 그리고 malloc으로 선언된 메모리는 힙(heap) 메모리에 저장이 된다.


malloc 사용법은 = (자료형*) malloc(사이즈); 이렇게 해주면 된다.

사이즈는 바이트 단위로 할당을 해준다.

코드를 보면서 익혀보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>
main()
{
    int size;
    int *arr;
 
    printf("몇개의 배열을 만들까요? : ");
    scanf_s("%d"&size);
 
    arr = (int*)malloc(sizeof(int)*size);
 
    for (int i = 0; i < size; i++)
        printf("%x "&arr[i]);
    printf("\n");
 
    free(arr);
}
cs

< 출력 >
몇개의 배열을 만들까요? : 3
eb4398 eb439c eb43a0

먼저 포인터 변수 arr 변수를 선언하고 동적 메모리 할당을 한다.
먼저 malloc(동적 메모리 할당)을 하는데 4*size 만큼 해줬다. 3을 입력 했기 때문에 4*3 = 12 바이트가 할당이 되었다.
그리고 함수 반환형이 void* 이기 때문에 int*로 강제 형 변환을 해주었다.
그리고 할당된 주소를 반환하니까 포인터 변수 arr에 주소를 넣어줬다.
출력을 보면 연속적으로 할당된 것이 보일 것이다.

그리고 free() 함수로 메모리를 해제를 한다.
해제하는 이유는 힙(heap) 메모리는 프로그래머가 직접 관리하기 때문에 우리가 직접 해제를 해줘야 한다.
그림으로 보면 이렇다.

그림상으로 보면 이렇게 되어있다.

단지 arr 변수는 주소만 참조하고 있을 뿐이다.


그럼 2차원 으로는 어떻게 만들까?

포인터 배열을 사용하면 된다. (아마 예상했을것이다)


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
32
33
34
35
36
37
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    int input, sum = 0;
    int subject, students;
    int **arr;
 
    printf("과목 수 : ");
    scanf_s("%d"&subject);
    printf("학생의 수 : ");
    scanf_s("%d"&students);
 
    arr = (int**)malloc(sizeof(int*)*subject);
 
    for (int i = 0; i < subject; i++)
        arr[i] = (int*)malloc(sizeof(int)*students);
    for (int i = 0; i < subject; i++) {
        printf("과목 %d 점수 --------\n", i);
        for (int j = 0; j < students; j++) {
            printf("학생 %d 점수 입력 : ", j);
            scanf_s("%d"&input);
            arr[i][j] = input;
        }
    }
    for (int i = 0; i < subject; i++) {
        sum = 0;
        for (int j = 0; j < students; j++)
            sum += arr[i][j];
        printf("과목 %d 평균 점수 : %d\n", i, sum / students);
    }
    for (int i = 0; i < subject; i++)
        free(arr[i]);
    free(arr);
    
    return 0;
}
cs

< 출력 >
과목 수 : 3
학생의 수 : 3
과목 0 점수 --------
학생 0 점수 입력 : 100
학생 1 점수 입력 : 90
학생 2 점수 입력 : 80
과목 1 점수 --------
학생 0 점수 입력 : 95
학생 1 점수 입력 : 85
학생 2 점수 입력 : 75
과목 2 점수 --------
학생 0 점수 입력 : 93
학생 1 점수 입력 : 83
학생 2 점수 입력 : 73
과목 0 평균 점수 : 90
과목 1 평균 점수 : 85
과목 2 평균 점수 : 83


동적 메모리 할당으로 한개의 과목의 평균을 알아보는 코드이다.
7행에서 주소를 담을 2차원 포인터 변수 arr을 만들어준다. (2차원 포인터는 1차원 포인터 주소를 담을 수 있다.)

14행은 그림으로 보면 이러하다.

이렇게 되어있다. 2차원 포인터 변수는 1차원 포인터 변수를 담을 수 있다는 점을 잊으면 안 된다.

17행에서 학생 수만큼 메모리를 할당해서 1차원 포인터로 강제 형 변환해주고 arr[i]에 넣어준다.

현재 arr[i]는 2차원 포인터 변수이니 1차원 포인터 변수를 담을 수가 있다.

그림으로 쉽게 이해해 보자.


이렇게 되어있다.

그리고 메모리 해제할때는 메모리 할당한 순서의 정 반대로 해제를 해주면 된다.

32,33행의 코드를 그림으로 보자.


먼저 1차원 포인터 *arr부터 해제를 해준다.

왜냐?? **arr을 먼저 해제하면 *arr들이 메모리 상에서 사라지게 되므로 가리키고 있던 배열들을 해제할 수 없게 되므로 오류가 일어난다.

그러므로 메모리 할당했던 순서의 정 반대로 해제를 해주면 된다.


근데 이렇게 만들어진 배열은 정확히 2차원 배열이라고 말하기가 힘들다.

배열은 메모리가 연속적으로 할당되어 있어야 하기 때문이다.

원래 2차원 배열은


이렇게 메모리에 연속적으로 할당이 되어야 한다.

그냥 우리가 만든 2차원 배열은 '2차원 배열 처럼 생긴 포인터 배열??'이라고 생각하면 된다.

우리가 만든 2차원 배열을 함수 인자값으로 넘겨보자.



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
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <stdio.h>
#include <stdlib.h>
void average(int **arr, int numStudent, int numSubject);
int main(void)
{
    int input, sum = 0;
    int subject, students;
    int **arr;
 
    printf("과목 수 : ");
    scanf_s("%d"&subject);
    printf("학생의 수 : ");
    scanf_s("%d"&students);
 
    arr = (int**)malloc(sizeof(int*)*subject);
 
    for (int i = 0; i < subject; i++)
        arr[i] = (int*)malloc(sizeof(int)*students);
    for (int i = 0; i < subject; i++) {
        printf("과목 %d 점수 --------\n", i);
        for (int j = 0; j < students; j++) {
            printf("학생 %d 점수 입력 : ", j);
            scanf_s("%d"&input);
            arr[i][j] = input;
        }
    }
    average(arr, students, subject);
 
    for (int i = 0; i < subject; i++)
        free(arr[i]);
    free(arr);
    
    return 0;
}
void average(int **arr, int numStudent, int numSubject)
{
    int sum;
    for (int i = 0; i < numSubject; i++) {
        sum = 0;
        for (int j = 0; j < numStudent; j++)
            sum += arr[i][j];
        printf("과목 %d 평균 점수 : %d\n", i, sum / numStudent);
    }
}
cs

출력은 똑같이 나온다.
35행을 보면 우리가 만든 2차원 배열을 **arr로 인자 값을 받고 있다. 우리가 만든 2차원 배열은 단순히 원소가
int* 형 원소들을 가지는 1차원 배열이기 때문이다. 1차원 배열을 함수의 인자로 넘겨줄 때에는 크기를 써 주지 않아도 되죠? 
그렇다고 해서 2차원 배열의 성질을 읽어버리는 것은 아니다. int **arr로 넘겨줬을 때 arr[3][3]과 같이 접근을 할 수가 있다.
그렇기에 우리가 만든 배열을 2차원 배열이라 부르는 것이다.
arr[3][3]은 *(*(arr+3)+3)으로 해석되는데, *(arr+3) 을 통해 arr의 세 번째 원소에 접근을 하게 되고
+3을 하면 int형 배열의 4번째 원소에 접근을 하게 된다.



calloc()에 대해서 알아보겠다.

calloc 함수의 원형
- void* calloc(size_t num, size_t size); 이다.
간단하게 말해서 사용법은 (자료형*)calloc(사이즈, 사이즈); 이렇게 선언한다
선언을 하면 사이즈 * 사이즈를 해서 메모리 할당이 된다.

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int *p1 = (int*)calloc(1sizeof(int)); // 1 * 4 = 4바이트
    int *p2 = (int*)malloc(4); // 4바이트
 
    printf("p1 값 : %d\n"*p1);
    printf("p2 값 : %d\n"*p2);
 
    free(p1), free(p2);
}
cs

< 출력 >
p1 값 : 0
p2 값 : -842150451

calloc은 값이 0으로 초기화 되는 반면 malloc는 쓰레기 값이 나온다.

* NOTE
  - calloc() 함수는 할당된 동적 메모리를 자동적으로 0으로 초기화 한다. 이런 초기화가 편리하기도 하지만 시간 낭비를 유발 할 수도 있음.

calloc 끝!!



realloc()에 대해서 알아보자.

realloc 함수의 원형
- void* realloc(void *p, size_t size); 이다.
함수의 이름을 보면 대충 짐작이 간다. "re" 다시 메모리를 할당하겠다. 이런말 갔다.
만약 malloc 함수로 메모리 할당을 했는데 사용중에 다시 할당된 것보다 크게 필요하거나 적게 필요할 때 사용된다.
예제를 통해 알아보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int *= (int*)malloc(sizeof(int* 2);
    p[0= 10, p[1= 20;
 
    p = (int*)realloc(p, sizeof(int* 4);
    p[2= 30, p[3= 40;
 
    for (int i = 0; i < 4; i++)
        printf("p[%d] : %d", i, p[i]);
    free(p);
}
cs

먼저 6행까지의 실행 내용을 그림으로 나타내보자.

6행까지의 내용은 이러하다.

8행에서 다시 16바이트로 재 할당을 했다


9행까지의 실행 내용은 이렇다.

여기서 메모리를 다시 할당해주면 다른곳에서 8바이트를 가져오나? 아니면 연속적으로 할당을 해주나? 라고 생각을 했다.

그래서 확인해봤다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int *= (int*)malloc(sizeof(int* 2);
    p[0= 10, p[1= 20;
    printf("%x %x\n"&p[0], &p[1]);
 
    p = (int*)realloc(p, sizeof(int* 4);
    p[2= 30, p[3= 40;
    printf("%x %x\n"&p[2], &p[3]);
 
    free(p);
}
cs


< 출력 >

7544e0 7544e4

7544e8 7544ec


연속적으로 할당되는것을 볼수가 있다.




그럼 여기서 구조체는 어떻게 동적 할당을 할까??


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
#include <stdio.h>
#include <stdlib.h>
struct Something
{
    int a, b;
}*arr;
int main()
{
    int size;
    printf("원하시는 구조체 배열의 크기 : ");
    scanf_s("%d"&size);
 
    arr = (struct Something*)malloc(sizeof(struct Something)*size);
 
    for (int i = 0; i < size; i++) {
        printf("arr[%d].a : ", i);
        scanf_s("%d"&arr[i].a);
        printf("arr[%d].b : ", i);
        scanf_s("%d"&arr[i].b);
        printf("arr[%d].a : %d\narr[%d].b : %d\n", i, arr[i].a, i, arr[i].b);
        printf("\n");
    }
 
    free(arr);
 
    return 0;
}
cs

< 출력 >
원하시는 구조체 배열의 크기 : 2
arr[0].a : 10
arr[0].b : 20
arr[0].a : 10
arr[0].b : 20

arr[1].a : 30
arr[1].b : 40
arr[1].a : 30
arr[1].b : 40

이렇게 하면 된다. 자세한 설명은 위에 설명을 들었으면 충분히 이해 하고도 남을 것이다.

댓글

Designed by JB FACTORY