동적할당을 연습하던 와중에 우리가 흔하게 사용하는
int *arr = new int[5];
delete [] arr;
해당 로직에서 어떻게 delete는 arr의 사이즈가 얼마나 될 줄 알고 delete를 시킬 수 있는 것인가?
arr에서 메모리 주소를 저장할 때 사이즈도 저장하는건가? 어떻게 이게 가능하지?
라는 의문이 생겼습니다.
int * arr = new int [5] ; 와 같은 형태를 지금부터는 배열 동적 할당 (new int[]) ,
그리고 int *arr = new int ; 와 같은 형태를 단일 객체 동적 할당(new int) 이라고 이야기하겠습니다.
배열 동적 할당 : 동적 배열의 크기를 컴파일러가 배열을 할당할 때 추가 메타데이터로 저장합니다.
다시 말해 , 배열을 할당할 때 할당할 크기에 4byte가 더 할당되는데
이 4byte에 배열의 크기가 저장됩니다.
단일 객체 동적 할당 : 배열을 할당할 때 해당 객체의 필요한 메모리만 저장됩니다.
다시말해 new int로 단일객체 생성 동적할당을 통해 배열을 만들기 위해서는 반복문을 사용하여 접근해야합니다.
왜냐하면 그때의 메서드에서는 size가 담기지 않기 때문입니다.
저는 이런 생각을 했습니다. 굳이 왜 배열 동적 할당을 해야하지 ?
그래서 실험을 해보기로 했습니다.
#include <iostream>
#include <chrono>
using namespace std;
using namespace chrono;
class Monster {
public:
Monster() {}
};
int main() {
const int N = 1000000; //testCase if N=100만
// 배열 동적 할당 테스트
//high_resolution_clock::now(); => 현재 시간을 나타내줌
auto start1 = high_resolution_clock::now(); //save StratTime
Monster* monsters = new Monster[N]; // new[]
delete[] monsters; // delete[]
auto end1 = high_resolution_clock::now(); // save endTime
// 단일 객체 동적 할당 테스트
auto start2 = high_resolution_clock::now();
Monster** monsterPtrs = new Monster * [N]; // 클래스에 대한 포인터를 여러개 만들것이기 때문에 2중 포인터 사용 monsterPtr에는 동적할당된 몬스터의 주소가 저장됨
for (int i = 0; i < N; ++i) {
monsterPtrs[i] = new Monster(); // for->new => 유사 new[]
}
for (int i = 0; i < N; ++i) {
delete monsterPtrs[i]; // delete => 유사 delete[]
}
delete[] monsterPtrs;
auto end2 = high_resolution_clock::now();
//duration_cast <시간형식> (바꿀 시간).count(정수로 반환) => 내가 확인할 시간을 , 무슨 시간의 형식을 사용하여 정수로 반환한다.
cout << "배열 동적 할당: " << duration_cast<milliseconds>(end1 - start1).count() << " ms\n";
cout << "단일 객체 동적 할당: " << duration_cast<milliseconds>(end2 - start2).count() << " ms\n";
return 0;
}
해당 코드의 실행결과는 다음과 같습니다.
해당결과를 보고 몇가지가 궁금해졌습니다.
1. 배열동적할당은 new를 한번만 쓰는것인가?
2. 배열동적할당의 size만큼 for문으로 단일객체동적할당을 반복한다면
결과물은 똑같은데 왜 속도에서 차이가 날까?
정답은 다음과 같았습니다.
1. 배열 동적 할당은 배열의 각 index마다 메모리를 할당하는 것은 단일 객체 동적 할당 for 문과 같았지만
다음과 같은 차이가 있습니다.
1-1 메모리 요청 횟수 :
- 배열 동적 할당 : 메모리 할당 요청을 한번만 하여 힙관리자 호출이 최소화 됩니다.
- 단일 객체 동적 할당 For문 : for문이 한번씩 실행될때마다 메모리 할당 요청이 되어, malloc함수나 힙관리자의 호출이
여러번 발생합니다. 이 과정에서 힙관리자는 적절한 메모리 블록을 찾고 관리정보를 저장합 니다.
1-2 단일 객체 동적 할당 지역성 :
- 그때그때 힙관리자를 호출하지만, 메모리 공간이 힙 영역에서 연속적이지 않을 수 있습니다.
1-2 배열 동적 할당 지역성 :
- 할당된 메모리는 연속된 힙 공간에 배치되고, 지역성이 높습니다. 또, 연속적으로 배치되어있다보니
메모리 접근 속도가 빨라집니다.
1-2 부연설명 :
만일 다음과 같은 코드를 실행한다라고 했을 때
Monster** monsterPtrs = new Monster * [N]; // 클래스에 대한 포인터를 여러개 만들것이기 때문에 2중 포인터 사용 monsterPtr에는 동적할당된 몬스터의 주소가 저장됨
for (int i = 0; i < N; ++i) {
monsterPtrs[i] = new Monster(); // for->new => 유사 new[]
}
for (int i = 0; i < N; ++i) {
delete monsterPtrs[i]; // delete => 유사 delete[]
}
포인터 배열 자체는 힙공간내에 연속적으로 존재하지만 각각의 포인터 배열안에 있는 주소 값은 불연속적일 수 있다.
마지막으로 다음과 같은 예시를 한번 보고 오늘의 TIL은 마치겠습니다.
만약에 Monster 클래스를 다음과 같이 동적할당을 받았습니다.
할당된 Monster 클래스를 메모리 할당 해제를 하는 방법은 1번과 2번중 어느것이 정답일까요?
정답은 2번입니다.
1번의 경우 배열 동적 할당으로 , NEW[] 를 통해 SIZE를 전달하지 않았기 때문에
힙영역 에러가 발생합니다.
출처 : https://learn.microsoft.com/ko-kr/cpp/cpp/delete-operator-cpp?view=msvc-170
https://learn.microsoft.com/ko-kr/cpp/cpp/new-and-delete-operators?view=msvc-170
'TIL' 카테고리의 다른 글
2025/01/02 TIL : Sort함수와 람다 (0) | 2025.01.03 |
---|---|
2024/12/31 TIL : 중첩 타입과 중첩클래스 (3) | 2024.12.31 |
2024/12/27 TIL : 오버로딩과 오버라이딩 (1) | 2024.12.27 |
2024/12/26 TIL : 가상함수와 메모리할당 (1) | 2024.12.26 |
2024/12/24 TIL : 생성자 (3) | 2024.12.24 |