assortrock

c++ 8,9일차

solie75 2022. 5. 19. 21:50

[메모리 할당]

프로그램에서 값을 저장하기 위해 필요한 기억 장소를 확보하는 것.

 

정적 할당

변수 선언 등으로 자료형에 따라 메모리 크기가 할당 되고 프로그램 실행할 때마다 변하지 않는 방식.

 

동적 할당

프로그램 작성 단계가 아닌 프로그램 실행 단계에서 결정 되는 크기에 따라 메모리를 할당해 주는 방식

(메모리를 할당하고 해제하는 과정이 필요)

 

[Heap 메모리 영역의 할당 방식]

 

총 20k(20000)바이트의 heap 메모리 영역이 존재할 때 다음과 같이 구성되어 있다고 가정해보자

A B C D E
used unused used used unused
4k 5k 3k 2k 6k

이때 malloc(6k)로 6000바이트 메모리를 할당받으면 가장 첫번째 A 영역이 있는 앞부분 부터 탐색을 시작하여 사용하지 않는 메모리를 찾는다. B구역이 안사용되고 있지만 5k 뿐이 되지 않아 계속 탐색하고 결국 가장 뒷부분인 E 영역에 할당을 한다 ( 할당 된 영역을 F  라 하자).

그리고 free(c) 로 3000 바이트의 영역을 해제하고 연달아 있는 B와 C 를 합쳐 G 영역이라 하자. 

이 다음으로  malloc(7k) 를 하게 되면 또 앞에서 부터 사용되지 않는 구역을 찾는다  그리고 G 영에 할당 되고 할당되지 않은 5k 를 비워둔다. 그러면 다음과 같다 (새로이 7k 가 할당된 구역을 H 그로인해 남은 구역을 I 라고 하자).

A H I D F
used used unused used used
4k 7k 1k 2k 6k

이런식으로 할당과 해제가 이루어 진다.

이때 이게 계속 되면 메모리의 단편화가 일어난다. 사용할 수 없는 단편적인 메모리 영역이 많아진다는 것이다.

 

 

[malloc( )]

void* malloc(size_t size)

size_t : 동적으로 할당할 메모리의 크기

반환값 : 성공시 할당한 메모리의 첫번째 주소 반환, 실패시 null반환

운영체제가 이제 사용할 수 있는 주소값을 준다(return 값으로)

주소값과 그 크기만 주기 때문에 어떠한 자료형을 정하지 않고 void 포인터 형태로 준다.

 

 

이때 malloc을 사용해 메모리를 할당 하는데에 있어서 어떠한 자료형인지는 정의 되지 않았기 때문에

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

와 같이  (int*)와 같이 정해 주어야한다.

 

[free( )]

int * arr = (int*)malloc(sizeof(int));
free(arr)

동적 메모리를 할당하면 힙 메모리에 공간이 생성 되고 이 힙 메모리는 프로그램이 종료될 때까지 존재하기에 메모리를 할당만 하고 해제를 하지 않으면 사용량이 계속해서 증가하게 된다. 즉 메모리 해제를 하지 않으면 메모리 사용량이 계속 증가하는 메모리 누수가 일어난다.

 

[calloc( )]

void* calloc(size_t elt_count, size_t elt_size)

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

* malloc 은 할당된 공간의 값들을 바꾸지 않는다 / calloc은 할당된 공간의 값을 모두 0으로 바꾼다.

 

[realloc( )]

int main()
{
	int* arr;
    arr = (int*)malloc(sizeof(int)*5);
    arr[4] = 1234;
    arr = (int*)realloc(arr, sizeof(int)*10);
    arr[9] = 5678;
    printf("arr[4]=%d, arr[9]=&d\n", arr[4], arr[9]);  // 출력 arr[4]=1234, arr[9] =5678
    
    free(arr);
}

첫번째 인수로 molloc이나 colloc 으로 이미 할당된 메모리의 시작 주소를, 두번째 인수로는 재할당할 크기를 전달한다.

첫 번째 인수가 null 인경우( 할당되어 있지 않은 경우) 새로 메모리를 할당하므로 realloc 은 malloc 과 같아진다.

두 번째 인수가 0 인 경우 할당을 취소하라는 말이므로 free와 같아진다. 

재할당 했을 때 원래 존재하던 곳이 재할당 한 크기만큼의 크기가 되지 않는다면 주소를 곪기지만 기존에 내용들을 그대로 복사하므로 재할당에 의해 위치는 바뀌더라도 내용은 그대로 유지된다.

 

[new( ), delete( )]

자료형* 변수명 = new 자료형  // 메모리 할당
delete 변수명  // 메모리 할당 해제
//배열 형태 메모리 할당
자료형 *변수명 = new 자료형[배열의크기]; //배열 형태 메모리 할당 ( 배열의 크기 == 요소의 개수)
delete[] 변수명;  //배열 형태의 메모리 할당 해제
int main()
{
	int *num1 = new int;
    *num1 = 100;
    
    std::cout<<*num1;  // 출력 100
    
    delete num1;
}

delete 연산자는 실제로 삭제하는 것은 없다. 단순히 가리키는 메모리를 다시 운영체제로 반환하는 것.

delete 연산자를 거치고 나서도 포인터 변수는 여전히 이전과 동일한 범위를 가지며 다른 변수와 마찬가지로 새 값을 할당받을 수 있다.

 

[댕글링 포인터(Dangling pointers)

c++에서는 운영체제에 반환되는 메모리에는 반환되기 전의 값과 같은 값이 포함되며, 포인터는 현태 할당된 메모리를 가리킨다.

여기에서 할당이 해제된 메모리를 가리키는 포인터를 댕글링 포인터(dangling pointer)라고한다. 댕글링 포인터를 역참조하거나 삭제하면 정의되지 않은 동작이 발생한다.

이를 방지하기 위해서

여러 포인터가 같은 동적 메모리를 가리키는 것을 피해야 하며

되도록 포인터를 삭제할 때 포인터를 0 또는 nu;llptr 로 설정하자.

 

[null 포인터와 동적 메모리 할당]

동적 메모리 할당과 관련하여 널 포인터는 기본저긍로 "이 포인터에 메모리가 할당되지 않았다"를 의미한다.

 

[malloc과 new 의 차이]

1. malloc 은 #include <stdio.h> 필요, new 는 기본으로 제공

2. malloc 은 사이즈를 매개변수로 받고, 리턴 타입이 void* 형이므로  sizeof() 가 필요, new 는 할당할 타입을 지정하면 알아서 할당할 타입의 포인터로 넘어온다.

3. malloc 은 초기값을 지정해 줄 수 없지만, new 는 할당과 동시에 초기화가 가능하다.

4. malloc 은 생성자 호출 안함, new 는 생성자(객체를 자동으로 초기화 해주는 함수)를 자동으로 호출한다. 

 

*malloc의 경우 , realloc 이라는 함수로 재할당이 가능하지만  new 는 이러한 것이 없어 '새로할당 -> 복사 -> 해제'의 과정을 거쳐야 한다. 객체가 아닌 경우에 재할당이 빈번하게 일어난다면, malloc/free가 더 좋은 선택이 될 수 있다.

 

[동적할당의 예시 : 배열 동적 할당(malloc)]

int main()
{
	int* ptr = (int*)malloc(sizeof(int)*10);
    for (int i = 0; i <10 ; i++)
    {
    	ptr[i] = i;
    }
    printf("%d", ptr[5]);  // 출력 5
    
    free(ptr);
}

malloc 으로 할당 받는 메모리의 자료형은 (int*)를 앞에 붙여서 int 형으로 정하고 sizeof()를 사용해서 배렬의 각 요소의 크기를 정한다. 그리고 sizeof함수에 배열의 길이가 되는 수(요소의 수)를 곱해서 할당받은 총 메모리 값을 malloc이 입력받게 된다.

 

[동적 할당의 예시: 배열 독정 할당(new)]

int *arr1 = new int[5]; // 배열 형태 메모리 동적 할당

for(int i=0; i <5 ; i++)
{
	arr1[i] = i;
}

for(int i=0; i<5; i++)
{
	std::cout << point[i];  //출력 01234
}

delete[ ] arr1;  // 배열 형태 메모리 할당 해제

[동적할당의 예시: 구조체]

struct tData
{
	int a;
    int b;
    char c;
    float f;
}
struct tData* p = (tData*)malloc(sizeof(tData));

(*p).a = 10;
p -> a = 10;

printf("%d" , p -> a);

가운데 두 줄은 같은 작용을 한다.