동적 할당에 대해 이해하기 위하여 일반적은 프로그래밍 언어의 메모리 모델을 살펴보도록 하겠습니다.
메모리 모델
Stack 영역에는 지역변수, 인자, 리턴 주소가 할당됩니다.
Heap 영역에는 동적 변수가 할당됩니다.
동적 변수는 함수와 독립적으로 존재하며
동적으로 생성하고 제거될 수 있는 변수입니다.
자바의 new 연산자를 사용하여 객체를 생성하면, 생성된 객체가 저장되는 영역이 바로 Heap 영역입니다.
동적 변수 만드는 방법
malloc을 사용합니다.
malloc에 대한 정보는 다음과 같습니다.
자바 | C | |
동적 변수 생성 명렁 | new() | malloc |
생성 시 전달되는 것 | 클래스 이름, 인자 | 생성될 메모리 크기 |
예시 | new A(); | malloc(sizeof(int)); malloc(24);//직접 바이트 수를 적기도 합니다. |
동적 변수의 초기화 | 생성자에서 진행 | X(안 함) |
생성 후 리턴 값 | 클래스 A타입 객체의 OID(Object ID) | void * |
생성 결과 활용 | A a = new A(); | int * p = (int *)malloc(sizeof(int)); |
삭제 명령 | X(불가능) | free() |
삭제 주체 | Garbage Colloctor | 개발자(프로그램에 명시) |
사용 예시
malloc을 사용하기 위해서는 stdlib.h를 include해야 합니다.
#include <stdio.h>
#include <stdlib.h>
int main(){
int *p;
p = (int *)malloc(sizeof(int));
*p = 3;
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int (*arr)[10];
arr = (int(*)[10] )malloc( sizeof(int(*)[10]) );
return 0;
}
주의할 점
malloc은 단지 메모리 공간을 할당하고, 할당된 주소를 반환 것일 뿐입니다.
되게 헷갈렸던 부분이 있었는데, 다음을 보고 설명하겠습니다.
int ** (**arr) [2][3] = malloc(??);
위의 코드가 원하는 데로 작동하려면 무엇을 넣어주어야 할까요?
8(32비트 운영체제라면 4), void *, int *, int ** (**) [2][3] 등 포인터의 크기와 같다면 모두 가능합니다.
또한 다음은 무엇이 들어가야 할까요?
int ** (*arr) [2][3] = malloc(??);
이는 목적에 따라 다릅니다.
만약 int** [2][3]인 다차원 배열을 n개 사용하고 싶은 경우, sizeof(int** [2][3]) * n가 될 것입니다.
이때는 위와 달리 포인터의 크기와 다른데, 그 이유는 malloc은 주소를 반환하기 때문에, 무조건 포인터로 하나 받아야 하기 때문입니다.
즉 쉽게 생각하면 위는 malloc을 통해 int ** [2][3]을 저장할 공간을 할당받고, 그에 &연산자를 취해 주소를 반환받은 것이므로
int ** (*arr) [2][3] 로 받아지는 것입니다.
Void *
어떤 타입의 주소라도 모두 담을 수 있는 타입입니다.
자바의 Object를 생각하시면 편한데, C언어에서 Void *로 생성된 변수는 주소이기만 하면 모두 담을 수 있는 변수입니다.
하지만 해당 포인터 변수의 값을 주소로 하여 내용을 읽어야하는데, 얼만큼 읽을지가 명시되어있지 않습니다.
따라서 내용을 읽어오기 위해서는 형변환이 필수입니다.
다음과 같이도 사용할 수 있습니다.
int isNull(void * target) {
return target == NULL;
}
동적변수의 삭제 - free
malloc으로 할당된 동적변수의 메모리를 해제시키는 작업입니다.
만약 삭제를 하지 않으면 메모리 누수등의 메모리 리소스 관련 문제가 발생할 수 있습니다.
문제가 발생하지 않더라도, 일단은 메모리는 낭비하는 것이기 때문에 반드시 사용 후에는 free를 시켜주어야 합니다.
사용법은 다음과 같습니다.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int (*arr)[10];
arr = (int(*)[10] )malloc( sizeof(int) );
//~~~작업들
free(arr);
return 0;
}
free시 주의점
#include <stdio.h>
#include <stdlib.h>
int main(){
int * ptr = malloc( sizeof(int) );
char * c1 = ptr;
char * c2 = ptr;
char * c3 = ptr;
char * c4 = ptr;
c2 = c2+1;
c3 = c3+2;
c4 = c4+3;
char cc1 = '1';
char cc2 = '2';
char cc3 = '3';
char cc4 = '4';
*c1 = cc1;
*c2 = cc2;
*c3 = cc3;
*c4 = cc4;
printf("%c\n", *c1);
printf("%c\n", *c2);
printf("%c\n", *c3);
printf("%c\n", *c4);
free(c1);
//free(ptr); OK
//free(c2); 오류
//free(c3); 오류
//free(c4); 오류
printf("%c\n", *c1);
return 0;
}
free는 항상 할당받은 주소에서 free해주어야 합니다.
위의 코드에서 c2,c3,c4 등을 free하게 되면 오류가 발생합니다.
free는 항상 할당받은 주소에서 free를 해주어야 합니다.
게다가 c1은 char의 포인터이고, ptr은 int의 포인터이지만 이것이 free에 영향을 미치지 않습니다.
NULL 포인터
메모리가 할당되지 않은 포인터입니다.
문제가 생겨서 malloc으로 인해 메모리 확보를 하지 못한다면 malloc은 NULL을 반환합니다.
Null은 0이고 '\0'이다.
int main(int argc, char const *argv[])
{
int * nullPtr = NULL;
int nullInt = NULL;
int zero = 0;
char nullStr = '\0';
char nullStr2 = NULL;
printf("nullPtr c: %c\n", nullPtr);
printf("nullPtr d: %d\n", nullPtr);
printf("nullPtr p: %p\n", nullPtr);
printf("nullPtr s: %s\n", nullPtr);
printf("nullInt c: %c\n", nullInt);
printf("nullInt d: %d\n", nullInt);
printf("nullInt p: %p\n", nullInt);
printf("nullInt s: %s\n", nullInt);
printf("zero c: %c\n", zero);
printf("zero d: %d\n", zero);
printf("zero p: %p\n", zero);
printf("zero s: %s\n", zero);
printf("nullStr c: %c\n", nullStr);
printf("nullStr d: %d\n", nullStr);
printf("nullStr p: %p\n", nullStr);
printf("nullStr s: %s\n", nullStr);
printf("nullStr2 c: %c\n", nullStr2);
printf("nullStr2 d: %d\n", nullStr2);
printf("nullStr2 p: %p\n", nullStr2);
printf("nullStr2 s: %s\n", nullStr2);
printf("isSame %d\n",nullPtr == NULL);
printf("isSame %d\n",nullPtr == nullInt);
printf("isSame %d\n",nullPtr == zero);
printf("isSame %d\n",nullPtr == nullStr);
printf("isSame %d\n",nullPtr == nullStr2);
printf("isSame %d\n",nullInt == NULL);
printf("isSame %d\n",nullInt == zero);
printf("isSame %d\n",nullInt == nullStr);
printf("isSame %d\n",nullInt == nullStr2);
printf("isSame %d\n",zero == NULL);
printf("isSame %d\n",zero == nullStr);
printf("isSame %d\n",zero == nullStr2);
printf("isSame %d\n",nullStr == NULL);
printf("isSame %d\n",nullStr == nullStr2);
return 0;
}
calloc, realloc
calloc : malloc과 동일하지만 인자의 전달방식이 조금 다르며, 메모리를 0으로 초기화합니다.
다음과 같이 사용합니다.
int * p = (int *)calloc(24, sizeof(int)); // (int *)malloc(sizeof(int) * 24)
다음과 같이 메모리를 0으로 초기화합니다.
int main() {
int * a = malloc(sizeof(int));
int * b = calloc(1, sizeof(int));
printf("결과 : %d\n", a);//???
printf("결과 : %d\n", b);//????
printf("결과 : %d\n", *a);//????
printf("결과 : %d\n", *b);//0
}
realloc : 추가 인자로 포인터를 받아 해당 포인터 주위의 공간이 넉넉하면 주위에 좀더 메모리를 확장 혹은 축소 후 해당 포인터를 리턴합니다.
만약 공간이 없다면 이전 포인터가 가리키는 공간을 반납하고, 새로운 공간을 확보하여 새로운 주로를 리턴합니다.
일반적인 정적 배열을 나타내는 포인터에 대해서는 사용이 불가능합니다.
int * p = (int *) malloc(sizeof(int));
p = (int *)realloc(p, 10 * sizeof(int));
Dangling Pointer
이미 free 된 메모리를 다른 포인터변수로 접근하게 되는 경우를 의미합니다.
int *p, *q;
p = (int *) malloc(sizeof(int));
*p = 10;
q = p;
free(p);
p = NULL; //해제 후 명시적으로 NULL을 넣어주는 것이 좋습니다.
*q = 20; // 오류, q가 Dangling Pointer입니다.
Memory Leak
Memory Leak은 프로그램이 불필요한 메모리를 계속 점유하는 현상입니다.
메모리 공간을 반납하지 않은 상태에서, 해당 메모리에 접근 불가(unreachable)가 되는 상황에 Memory Leak이 발생하며,
접근이 가능하더라도, 이를 해제하지 않고 프로그램이 지속된다면 Memory Leak이 발생합니다.
int * p = (int *) malloc(sizeof(int));
int * q = (int *) malloc(sizeof(int));
*p = 10;
*q = 20;
p = q;//q가 가리키는 메모리 공간에 도달할 방법이 없습니다 -> Memory Leak!
'c언어' 카테고리의 다른 글
[C언어] - typedef (0) | 2022.06.12 |
---|---|
[C언어] - 구조체 (Struct) (0) | 2022.06.12 |
[C언어] - 이중 포인터 (0) | 2022.05.06 |
[C언어] - 다차원 배열 (0) | 2022.05.06 |
[C언어] - Const (0) | 2022.04.20 |