Coding Memo
메모리 할당 - Stomp Allocator 본문
본 포스팅은 인프런에 등록되어 있는 Rockiss 님의 강의를 보고 간단하게 작성한 글입니다.
기존 Allocator에서 발생될 수 있는 문제 (에러조차 안뜰 수 있는 치명적인 문제들!)
첫 번째 문제
int main()
{
vector<int> v{ 0, 1, 2, 3, 4 };
for (int i = 0; i < 5; i++)
{
int value = v[i];
if (value == 1)
{
v.clear();
}
}
}
value==1 일때 vector를 clear하고 v[i]로 접근하려고 하면 문제가 발생한다.
이 문제는 런타임에러가 바로 발생하여 바로 문제를 찾을 수 있다. (vector에 의해서)
두 번째 문제
class Unit
{
public:
Unit()
{
}
Unit(int hp) : _hp(hp)
{
}
~Unit()
{
}
int _hp = 10;
};
int main()
{
Unit* unit = new Unit();
delete unit;
// 다른 코드로 인해 unit이 할당되었던 메모리가 다른 곳에서 사용되었다고 가정
unit->_hp = 100; // ??
cout << unit->_hp << endl;
}
이번 코드는 오류를 일으키지 않는다! (...)
당장 위 코드의 결과는 100이 출력이 되겠지만, 주석에서 언급했던 것처럼 unit이 할당되었던 메모리가 다른 곳에서 사용되었다면 _hp값은 제대로 나오지 않을 것이다.
free 된 이후에 해당 메모리를 다시 가져다 쓰려고 하면 치명적인 메모리 오염문제를 일으킬 수 있다.
(Use-After-Free 문제)
-> delete 한 후에 nullptr로 초기화 시켜주면 해결될까?
-> 해결되긴하지만 매번 그렇게 하기 매우 불편하기도 하고 여전히 unit에 그래도 접근은 가능한 상태이다.
세 번째 문제
캐스팅 했을 때 오버플로우와 비슷한 문제가 발생한다.
class PUnit
{
public:
PUnit() {};
virtual ~PUnit() {};
};
class Unit : public PUnit
{
public:
Unit()
{
}
Unit(int hp) : _hp(hp)
{
}
~Unit()
{
}
int _hp = 100;
};
int main()
{
PUnit* p = new PUnit();
Unit* u = static_cast<Unit*>(p);
u->_hp = 10; // ?
}
PUnit의 크기보다 Unit의 크기가 더 크기 때문에 더 많은 메모리 사이즈를 가지고 있다. 따라서 캐스팅 시 Unit의 맴버 필드 값이 초기화 되지 않아 쓰레기 값을 가지고 있는 것을 확인 할 수 있다. (PUnit은 _hp에 대한 메모리가 없었으므로)
마찬가지로
즉, 해당 값을 다른 값으로 바로 초기화해주거나 적당한 값으로 할당한다면 문제가 되지 않을 수도 있지만, 이 값을 read 하려고 하면 큰 문제가 발생한다.
위 문제들의 발생을 막기위해 Stomp Allocator를 구현한다.
단, 릴리즈 버전이 아닌 디버그 모드에서만 작동하도록 한다.
page 단위로 할당을 할 것이다.
페이지 크기는 4KB로 잡자.
int main()
{
SYSTEM_INFO info;
::GetSystemInfo(&info);
cout << info.dwPageSize << endl; // 4096 (4KB, 0x1000)
cout << info.dwAllocationGranularity << endl; // 65536 (64KB, 0x10000)
}
VirtualAlloc과 VirtualFree을 사용한다.
https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
VirtualAlloc으로 메모리를 할당할 때 사이즈나 읽기 전용, 쓰기 전용등의 여러가지 정책으로 구체적이게 할당할 수 있다.
VirtualFree도 마찬가지로 메모리 해제시에 여러가지 정책을 지정할 수 있다.
VirtualFree를 통해 메모리를 해제하게 되면, 코드 상 다시 그 메모리에 같은 변수로 접근하려고 하면 에러가 발생한다. (delete로 해제했을 때는 다시 그 메모리에 접근할 때 에러가 발생하지 않았다는 것을 기억해보자.)
int main()
{
int* t = (int*)::VirtualAlloc(NULL, 4, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
::VirtualFree(t, 0, MEM_RELEASE);
*t = 100; // error occurs!!!
}
BaseAllocator와 마찬가지로 Alloc 함수와 Release 함수를 선언하고 추가적으로 Page 크기를 정해두자.
class StompAllocator
{
enum
{
PAGE_SIZE = 0x1000, // 4KB
};
public:
static void* Alloc(int size);
static void Release(void* ptr);
};
위에서 언급된 3번째 문제(캐스팅에서의 메모리 오염 및 접근 문제)를 해결하기 위해, 실제 데이터가 배치되는 부분을 할당된 메모리의 끝에 둔다.
<<앞에다 실제 메모리를 둘 경우>>
캐스팅 후에, hp가 할당된 메모리 범위 내에 존재하고 있을 수 있기 때문에 값을 읽거나 써도 에러가 발생하지 않는다.
-> 나중에 큰 메모리 오염 문제를 일으킬 수 있다.
<<뒤에다 실제 메모리를 둘 경우>>
캐스팅 후에, hp가 할당된 메모리 밖에 존재하기 때문에 _hp에 접근하려고 하면 에러가 발생하게 된다!!!!!!!
byte 단위(__int8)로 할당하기 위해 캐스팅 한 후에 return 해주도록 하자.
Release도 메모리 위치를 바꿔주었으니 baseAddress를 계산하여 free 하도록 하자.
void* StompAllocator::Alloc(int32 size)
{
const long long pageCount = (size + PAGE_SIZE - 1) / PAGE_SIZE;
const long long dataOffset = pageCount * PAGE_SIZE - size; // 메모리 뒤쪽부터 시작
// 메모리의 시작 부분(데이터 사이즈)을 메모리 뒷쪽에 배치하기 (overflow 방지, but underflow 문제가 있긴함)
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 long long address = reinterpret_cast<long long>(ptr);
const long long baseAddress = address - (address % PAGE_SIZE);
::VirtualFree(reinterpret_cast<void*>(baseAddress), 0, MEM_RELEASE);
}
마지막으로 debug 시에만 오류를 확인하기 위해 사용할 것이므로 다음과 같이 매크로에 추가한다.
#ifdef _DEBUG
#define xxalloc(size) StompAllocator::Alloc(size)
#define xxrelease(ptr) StompAllocator::Release(ptr)
#else
#define xxalloc(size) BaseAllocator::Alloc(size)
#define xxrelease(ptr) BaseAllocator::Release(ptr)
#endif // _DEBUG
대박! 이걸 어떻게 생각해낸거지........
'Game Server (C++)' 카테고리의 다른 글
Memory Pool (0) | 2022.10.09 |
---|---|
메모리 할당 - STL Allocator (0) | 2022.10.09 |
메모리 할당 - Allocator (0) | 2022.10.06 |
Reference Counting (0) | 2022.09.30 |
Lock 구현 (Read-Write Lock) (1) | 2022.09.26 |