먼저 선행해서 보면 좋은 부분 memory pool 을 하기 위한 new 오버로딩

https://3dmpengines.tistory.com/1399

 

placement new , 생성자를 원하는 시점에 호출하기

operator new, operator delete 연산자 C++에서 동적 객체를 생성하기 위해 일반적으로 new 연산자를 사용하는데, new 연산자는 2가지의 동작단계를 지니고 있습니다. 요청한 타입의 객체를 담을수 있는 크

3dmpengines.tistory.com

 

 

메모리가 오염되어 잘못 건드릴때나 엉뚱한 곳의 메모리를 건드려 나중에(몇시간, 또는 며칠) 시간이 한참 지나서 크래쉬가 나는현상을 격을 수 있는데 그럴때의 대비책이다

 

 

 


 

 

 

++ 기본 new와 delete, malloc, free를 쓰면 해제된 메모리에 접근했는데도 crash가 발생하지 않는 문제가 있다.

엉뚱한 메모리를 오염시키고 결국 오류가 나는 곳은 문제의 원인과 멀리 떨어져있는 경우가 많아서 디버그 하기도 쉽지않다.

// Case 1
Knight* k1 = new Knight();
k1->_hp = 200;
k1->_mp = 50;
delete k1;
k1->_hp = 100; // User-After-Free

// Case 2
vector<int32> v{ 1,2,3,4,5 };
for (int32 i = 0; i < 5; i++)
{
	int32 value = v[i];
	// TODO
	if (value == 3)
	{
		v.clear(); // 이후부터 유효하지 않은데, 또 접근.. 메모리 오염
	}
}

// Case 3
Player* p = new Player();
Knight* k = static_cast<Knight*>(p); // casting 오류 ,, dynamic_cast를 사용하고 nullptr를 체크하면 되지만 느림.
k->_hp = 100; // 메모리 오염

 

OS는 가상메모리를 사용한다. 프로그램에서 메모리를 요구하면 실제 메모리를 할당한 후, 물리 주소를 반환하는 게 아니라 물리주소에 매핑된 가상메모리를 반환한다. 따라서 다른 프로그램에서 같은 메모리를 주소를 참조한다고 하여도 실제 물리 주소는 다르다. 

 

운영체제는 메모리 할당 최소단위가 있어 아무리 잘게 메모리 할당을 요청해도 최소단위 만큼 메모리를 할당한다. 최소단위는 페이자 불리는 동일한 크기의 여러 구역으로 나뉜다. 이러한 정책을 페이징이라고 한다. 각 페이지마다 보안정책을 설정할 수 있다.

 

4KB = 4 * 1024 : 16진수로 0x1000 과 같음

4KB = 65536Byte = 64 * 1024 : 16 진수로 0x10000 과 같음

 

아래 함수에서 virtual alloc 으로 메모리를 잡을때 페이지 단위 크기로 잡는다(allocSize 을 4로 해놨어도)

즉 메모리를 페이지 단위로 관리해준다

::VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

 

 

2GB 면 64KB 로 페이지 잡는다고 할떄 32,768 개수 만큼을 잡을 수 있다

32,768 * 64 = 2,097,152 KB = 2GB

 

 

// 2GB [                      ]
// 2GB [ooxxooxxoxooooxoxoooos]
// 페이징 정책을 쓴다 4Kb[r][w][rw][][][][][] 페이지에 보안정책을 설정할 수 있다.

SYSTEM_INFO info;
::GetSystemInfo(&info);

info.dwPageSize; // 4KB (0x1000)
info.dwAllocationGranularity; // 64KB (0x10000) 메모리 할당할 때 이 숫자의 배수로 할당한다.

즉 페이지를 64kb 배수로 할당 하겠다는 것이고 메모리를 이 페이지 단위로 관리된다

dwAllocationGranularity = 64KB

 

그리고 이 페이지에 실제 메모리에 대한 권한 w,r, rw 등의 권한을 주어 보안 관리를 할 수 있고

dwAllocationGranularity 의 목적은 너무 세분화 되면 효율이 떨어지기 때문에 이것을 막기위해 64KB 로 설정된다

(이것은 운영체제마다 달라질 수 있겠지만)

 

 

운영체제에 직접 메모리 할당, 반환을 요청하는 API가 있는데 이를 이용하면 잘못된 주소를 참조할 때 바로 crash가 발생한다.

 

윈도우에서 할당 요청 함수는 VirtualAlloc, 반환 함수는 VirtualFree이다. 이를 이용해 StompAllocator를 만들어보자.

Allocator.h, Allocator.cpp

/*---------------------
	StompAllocator
---------------------*/

class StompAllocator
{
	enum { PAGE_SIZE = 0x1000 };

public:
	static void* Alloc(int32 size);
	static void Release(void* ptr);
};
/*---------------------
	StompAllocator
---------------------*/

void* StompAllocator::Alloc(int32 size)
{
	const int64 pageCount = (size + PAGE_SIZE - 1) / PAGE_SIZE;
	const int64 dataOffset = pageCount * PAGE_SIZE - size;

	void* baseAddress = ::VirtualAlloc(NULL, pageCount * PAGE_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
	return static_cast<void*>(static_cast<int8*>(baseAddress) + dataOffset);
}

void StompAllocator::Release(void* ptr)
{
	const int64 address = reinterpret_cast<int64>(ptr);
	const int64 baseAddress = address - (address % PAGE_SIZE);
	::VirtualFree(reinterpret_cast<void*>(baseAddress), 0, MEM_RELEASE);
}

pageCount는 가장 작은 PAGE_SIZE의 배수를 찾는다. pageCount * PAGE_SIZE가 요청할 메모리 크기이다.

dataOffset은 메모리 overflow를 찾기 위해 필요하다. offset없이 할당하면

[[       ]                 ] 와 같이 할당된다. 실제 사용메모리 부분을 할당 메모리 끝쪽에 위치시키면

overflow가 났을 때 잡아준다. [                [       ]]

 

메모리 반환하기 위해서 다시 원래 할당 주소를 찾아야 한다. address % PAGE_SIZE으로 offset을 구하고 그만큼 앞으로 이동한 포인터를 해제한다. 

 

메모리 alloc release 매크로를 바꾸면 모든 코드에 적용되어 편리하게 사용할 수 있다.

CoreMacro.h

	/*----------------------
			Memory
	----------------------*/
#ifdef _DEBUG
	#define x_alloc(size) StompAllocator::Alloc(size)
	#define x_release(ptr) StompAllocator::Release(ptr)
#else
	#define x_alloc(size) BaseAllocator::Alloc(size)
	#define x_release(ptr) BaseAllocator::Release(ptr)
#endif
// overflow 문제 잡기
//[                         [    ]]
Knight* knight = (Knight*)xnew<Player>();

knight->_hp = 100;
xdelete(knight);

User-After-Free 뿐 아니라 메모리 overflow 상황도 대응가능하다.

 

 

 

 


Custom해서 만든 allocator를 stl에서도 쓸 수 있다. 약간의 코드 작업이 필요하다. 먼저 stl에서 기대하는 API를 구현한 allocator가 필요하다.

Allocator.h

/*---------------------
	STL Allocator
---------------------*/

template<typename T>
class StlAllocator
{
public:
	using value_type = T;

	StlAllocator() {}

	template<typename Other>
	StlAllocator(const StlAllocator<Other>&) {}

	T* allocate(size_t count)
	{
		const int32 size = static_cast<int32>(count * sizeof(T));
		return static_cast<T*>( x_alloc (size));
	}

	void deallocate(T* ptr, size_t count)
	{
		x_release (ptr);
	}
};

 

allocate, deallocate에서 count는 메모리 사이즈가 아니라 할당할 메모리 개수이다.

 

StlAllocator를 사용한 stl container들을 재정의한다.

CorePch.h

#pragma once
...
#include "Container.h"
...

Container.h

#pragma once

#include "Types.h"
#include "Allocator.h"

#include <vector>
#include <list>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
using namespace std;

template<typename Type>
using Vector = vector<Type, StlAllocator<Type>>;

template<typename Type>
using List = list<Type, StlAllocator<Type>>;

template<typename Key, typename Type, typename Pred = less<Key>>
using Map = map<Key, Type, Pred, StlAllocator<pair<const Key, Type>>>;

template<typename Key, typename Pred = less<Key>>
using Set = set<Key, Pred, StlAllocator<Key>>;

template<typename Type>
using Deque = deque<Type, StlAllocator<Type>>;

template<typename Type, typename Container = Deque<Type>>
using Queue = queue<Type, Container>;

template<typename Type, typename Container = Deque<Type>>
using Stack = stack<Type, Container>;

template<typename Type, typename Container = Vector<Type>, typename Pred = less<typename Container::value_type>>
using PriorityQueue = priority_queue<Type, Container, Pred>;

using String = basic_string<char, char_traits<char>, StlAllocator<char>>;

using WString = basic_string<wchar_t, char_traits<wchar_t>, StlAllocator<wchar_t>>;

template<typename Key, typename Type, typename Hasher = hash<Key>, typename KeyEq = equal_to<Key>>
using HashMap = unordered_map<Key, Type, Hasher, KeyEq, StlAllocator<pair<const Key, Type>>>;

template<typename Key, typename Hasher = hash<Key>, typename KeyEq = equal_to<Key>>
using HashSet = unordered_set<Key, Hasher, KeyEq, StlAllocator<Key>>;

 

아래와 같이 사용하면 된다.

int main()
{
	Vector<Knight> v;
	HashMap<int32, Knight> map;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ref : https://dockdocklife.tistory.com/entry/STL-allocator?category=985040 

ref : https://dockdocklife.tistory.com/entry/Stomp-allocator

반응형

+ Recent posts