태크놀로지

[C++] vector 메모리 관리 구조 분석 본문

C++

[C++] vector 메모리 관리 구조 분석

원택 2020. 8. 7. 15:32

STL std::vector

vector가 어떤 방식으로 작동하고 메모리를 관리하는지에 대해 이해해봅시다. vector 컨테이너는 자동으로 메모리가 할당되는 배열입니다. 랜덤 엑세스 구조로 순회하는데는 최고의 성능을 보여주지만, 중간값을 삽입 삭제시 복사 이동연산으로 인해 효율이 많이 떨어지게 됩니다.

 


std::vector 메모리 할당 과정

#include <iostream>
#include <vector>

using namespace std;

class TEST {
private:
	int data;

public:
	// 복사 생성자
	TEST(const TEST& other)
	{
		cout << this << " - 복사연산자 호출" << endl;
		data = other.data;
	}
	TEST& operator=(const TEST& other)
	{
		cout << this << " - 복사할당연산자" << endl;
		data = other.data;
		return *this;
	}
	// 이동생성자
	TEST(TEST&& other)
	{
		// 기존 데이터 메모리해제
		if (data) data = NULL;

		// 데이터 복사
		data = other.data;

		// 이동 데이터 메모리해제
		other.data = NULL;

		cout << this << " - 이동생성자 호출" << endl;
	}

	TEST& operator=(TEST&& other)
	{
		// 기존 데이터 메모리해제
		if (data) data = NULL;

		// 데이터 복사
		data = other.data;

		// 이동 데이터 메모리해제
		other.data = NULL;

		cout << this << " 이동대입연산자" << endl;
		return *this;
	}

public:
	TEST() { cout << this << " - 생성자 호출" << endl; }
	~TEST() { cout << this << " - 소멸자 호출" << endl; }
};

int main()
{
	std::vector<TEST> tests;

	cout << "vector size: " << tests.size() << endl;
	cout << "vector capacity: " << tests.capacity() << endl;

	for (int i = 0; i < 10; ++i)
	{
		cout << endl;
		cout << "=======================================" << endl;
		tests.emplace_back(TEST());
		cout << "vector size: " << tests.size() << endl;
		cout << "vector capacity: " << tests.capacity() << endl;
	}
}

TEST 클래스의 생성자, 소멸자, 복사, 이동생성자의 출력을 찍고 vector에 객체를 넣어봅시다. 그리고 size와 capacity가 어떻게 변화되는지를 분석해봅시다.

 

결과창

vector size: 0
vector capacity: 0

=======================================
0000009E202FF904 - 생성자 호출
00000291BDCD2A60 - 이동생성자 호출
0000009E202FF904 - 소멸자 호출
vector size: 1
vector capacity: 1

=======================================
0000009E202FF904 - 생성자 호출
00000291BDCD4CF4 - 이동생성자 호출
00000291BDCD4CF0 - 복사연산자 호출
00000291BDCD2A60 - 소멸자 호출
0000009E202FF904 - 소멸자 호출
vector size: 2
vector capacity: 2

=======================================
0000009E202FF904 - 생성자 호출
00000291BDCD46B8 - 이동생성자 호출
00000291BDCD46B0 - 복사연산자 호출
00000291BDCD46B4 - 복사연산자 호출
00000291BDCD4CF0 - 소멸자 호출
00000291BDCD4CF4 - 소멸자 호출
0000009E202FF904 - 소멸자 호출
vector size: 3
vector capacity: 3

=======================================
0000009E202FF904 - 생성자 호출
00000291BDCD48EC - 이동생성자 호출
00000291BDCD48E0 - 복사연산자 호출
00000291BDCD48E4 - 복사연산자 호출
00000291BDCD48E8 - 복사연산자 호출
00000291BDCD46B0 - 소멸자 호출
00000291BDCD46B4 - 소멸자 호출
00000291BDCD46B8 - 소멸자 호출
0000009E202FF904 - 소멸자 호출
vector size: 4
vector capacity: 4

=======================================
0000009E202FF904 - 생성자 호출
00000291BDCD3B80 - 이동생성자 호출
00000291BDCD3B70 - 복사연산자 호출
00000291BDCD3B74 - 복사연산자 호출
00000291BDCD3B78 - 복사연산자 호출
00000291BDCD3B7C - 복사연산자 호출
00000291BDCD48E0 - 소멸자 호출
00000291BDCD48E4 - 소멸자 호출
00000291BDCD48E8 - 소멸자 호출
00000291BDCD48EC - 소멸자 호출
0000009E202FF904 - 소멸자 호출
vector size: 5
vector capacity: 6

=======================================
0000009E202FF904 - 생성자 호출
00000291BDCD3B84 - 이동생성자 호출
0000009E202FF904 - 소멸자 호출
vector size: 6
vector capacity: 6

=======================================
0000009E202FF904 - 생성자 호출
00000291BDCD6A48 - 이동생성자 호출
00000291BDCD6A30 - 복사연산자 호출
00000291BDCD6A34 - 복사연산자 호출
00000291BDCD6A38 - 복사연산자 호출
00000291BDCD6A3C - 복사연산자 호출
00000291BDCD6A40 - 복사연산자 호출
00000291BDCD6A44 - 복사연산자 호출
00000291BDCD3B70 - 소멸자 호출
00000291BDCD3B74 - 소멸자 호출
00000291BDCD3B78 - 소멸자 호출
00000291BDCD3B7C - 소멸자 호출
00000291BDCD3B80 - 소멸자 호출
00000291BDCD3B84 - 소멸자 호출
0000009E202FF904 - 소멸자 호출
vector size: 7
vector capacity: 9

=======================================
0000009E202FF904 - 생성자 호출
00000291BDCD6A4C - 이동생성자 호출
0000009E202FF904 - 소멸자 호출
vector size: 8
vector capacity: 9

=======================================
0000009E202FF904 - 생성자 호출
00000291BDCD6A50 - 이동생성자 호출
0000009E202FF904 - 소멸자 호출
vector size: 9
vector capacity: 9

=======================================
0000009E202FF904 - 생성자 호출
00000291BDCD7604 - 이동생성자 호출
00000291BDCD75E0 - 복사연산자 호출
00000291BDCD75E4 - 복사연산자 호출
00000291BDCD75E8 - 복사연산자 호출
00000291BDCD75EC - 복사연산자 호출
00000291BDCD75F0 - 복사연산자 호출
00000291BDCD75F4 - 복사연산자 호출
00000291BDCD75F8 - 복사연산자 호출
00000291BDCD75FC - 복사연산자 호출
00000291BDCD7600 - 복사연산자 호출
00000291BDCD6A30 - 소멸자 호출
00000291BDCD6A34 - 소멸자 호출
00000291BDCD6A38 - 소멸자 호출
00000291BDCD6A3C - 소멸자 호출
00000291BDCD6A40 - 소멸자 호출
00000291BDCD6A44 - 소멸자 호출
00000291BDCD6A48 - 소멸자 호출
00000291BDCD6A4C - 소멸자 호출
00000291BDCD6A50 - 소멸자 호출
0000009E202FF904 - 소멸자 호출
vector size: 10
vector capacity: 13
00000291BDCD75E0 - 소멸자 호출
00000291BDCD75E4 - 소멸자 호출
00000291BDCD75E8 - 소멸자 호출
00000291BDCD75EC - 소멸자 호출
00000291BDCD75F0 - 소멸자 호출
00000291BDCD75F4 - 소멸자 호출
00000291BDCD75F8 - 소멸자 호출
00000291BDCD75FC - 소멸자 호출
00000291BDCD7600 - 소멸자 호출
00000291BDCD7604 - 소멸자 호출

D:\STL MemoryStructure\2020\Project\wt_vector\x64\Debug\wt_vector.exe(프로세스 25568개)이(가) 종료되었습니다(코드: 0개).이 창을 닫으려면 아무 키나 누르세요...

 reserve를 하지않고 vector에 값을 넣었을경우, 초반엔 하나하나씩 메모리를 할당하다가 점점 메모리 할당부분을 늘려가고 있습니다. 계속해서 메모리 할당을 하게되는 오버헤드를 줄이기위해 개수가 늘어날수록 점점 더 많은 메모리를 한꺼번에 할당받습니다.

 

또한 생성, 소멸, 이동, 복사에 관련된 로그를 확인하면, 기존 capacity보다 size가 커질경우 기존에 있던 모든 객체들을 새로 할당한 영역에 복사하는 것들을 볼수있습니다.

 

※ 만약 엄청나게 많은 객체가 예약되지 않은 벡터에 저장된다면, 그만큼 오버헤드가 크니 꼭 reserve를 해줍시다!

 


다음 포스팅에서는 벡터에서 중간에 값을 삽입하거나 삭제할경우, 메모리 구조가 어떻게 변경되는지를 확인해보도록 하겠습니다.