Microsoft MVP성태의 닷넷 이야기
VC++: 107. VirtualAlloc, HeapAlloc, GlobalAlloc, LocalAlloc, malloc, new의 차이점 [링크 복사], [링크+제목 복사],
조회: 15114
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
[malloc.zip]    
(연관된 글이 2개 있습니다.)

VirtualAlloc, HeapAlloc, GlobalAlloc, LocalAlloc, malloc, new의 차이점

우선, 모든 할당 방식의 근간인 VirtualAlloc은 다음의 글을 읽어보시면 됩니다.

작업 관리자에서의 "Commit size"가 가리키는 메모리의 의미
; https://www.sysnet.pe.kr/2/0/1850

말 그대로, '가상 메모리'를 확보하는 함수로 HeapAlloc, GlobalAlloc, LocalAlloc, malloc, new로 할당받은 모든 메모리는 결국 VirtualAlloc으로 이미 할당받았던 영역을 기반으로 합니다.

그다음, GlobalAlloc과 LocalAlloc을 볼까요?

HGLOBAL WINAPI GlobalAlloc(_In_ UINT uFlags, _In_ SIZE_T dwBytes)
; https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalalloc

HLOCAL WINAPI LocalAlloc(_In_ UINT uFlags,_In_ SIZE_T uBytes)
; https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-localalloc

예전 16비트 윈도우 시대에 나누어진 것으로 다음의 글에 보면,

What was the difference between LocalAlloc and GlobalAlloc?
; https://devblogs.microsoft.com/oldnewthing/?p=37433

A look back at memory models in 16-bit MS-DOS
; https://devblogs.microsoft.com/oldnewthing/?p=104012

CPU의 Real 모드 메모리 관리에 따라 Segment 레지스터로 64KB까지 메모리가 구별되던 시절 GlobalAlloc은 Far *, LocalAlloc은 Near * 포인터를 다뤘습니다.

하지만 32비트 윈도우로 오면서,

Comparing Memory Allocation Methods
; https://learn.microsoft.com/en-us/windows/win32/memory/comparing-memory-allocation-methods

프로세스마다 단일하게 할당된 기본 힙(process's default heap)을 대상으로 HeapAlloc을 사용하는 각자의 래퍼 함수로 바뀌었습니다. 이 때문에 별도로 HeapCreate/HeapAlloc을 호출해 사용하는 것보다 오버헤드가 더 크다고 합니다. (의미인즉, 단일하게 운영되고 있는 "process's default heap"에 대해 경쟁할 것이기 때문에.)

마이크로소프트는 현재 GlobalAlloc의 경우 DDE, 클립보드, OLE data object 영역을 제외하고는 Heap 함수(HeapAlloc 등)를 쓰라고 권장하고 있으며 LocalAlloc은 특별한 사용처가 없으므로 모든 경우에 Heap 함수를 대신 쓰면 됩니다. 참고로, 사용 중인 3rd-party DLL 중에 LocalAlloc으로 할당받은 인자를 요구하는 경우가 있다면 그에 맞춰주는 정도가 있으며, LocalFree의 경우에는 일부 Win32 함수(예: FORMAT_MESSAGE_ALLOCATE_BUFFER 옵션이 지정된 FormatMessage)의 메모리를 해제할 때 사용해야 합니다. (그나마도 Windows 10부터는 HeapFree를 쓰라고 되어 있습니다.)

문서 상으로 GlobalAlloc과 LocalAlloc의 가장 큰 차이점은, GlobalAlloc의 경우 8바이트 정렬로 메모리 할당을 하기 때문에 실제 요구된 바이트보다 더 클 수 있다고 명시하는 반면 LocalAlloc은 8바이트 정렬이라는 내용은 없지만 마찬가지로 실제 요구된 바이트보다 더 클 수 있다는 식입니다. 결국, 같은 HeapAlloc을 사용하지만 약간의 내부적인 구현 상의 차이가 있다는 것인데 이 때문인지 마이크로소프트는 GlobalAlloc으로 할당받은 것은 반드시 GlobalFree로, LocalAlloc으로 할당받은 것은 LocalFree로 해제하라고 명시하고 있습니다.

HeapAlloc은 VirtualAlloc으로 할당한 메모리를 기반으로 바로 위에서 동작하는 함수입니다. 또한 기본적으로 process's default heap이 하나 제공되어 그곳으로부터 HeapAlloc을 통해 메모리를 할당받을 수도 있지만, 별도로 HeapCreate를 사용해 별도의 heap 영역을 만드는 것도 가능합니다.

여기까지가, 운영체제 종속적인 메모리 할당 API입니다. 당연히 Linux로 가면 위의 함수들은 그에 대응하는 Linux 함수로 대체된다고 보시면 됩니다.



이제 남은 것은 malloc과 new인데요.

우선, malloc은 C 런타임 라이브러리에서 구현한 메모리 할당 함수입니다.

malloc() 작동 원리
; http://egloos.zum.com/minjang/v/1232908

즉, 런타임 라이브러리가 어떻게 malloc을 구현했느냐에 따라 달라질 수 있습니다. 가령 같은 Visual C++ 컴파일러라도 버전에 따라 바뀌는 MSVCRT 라이브러리(예: msvcr100.dll과 msvcr120.dll)에서조차도 구현 방법이 다를 수 있고, 심지어 디버깅 정보로 인해 msvcr100.dll과 msvcr100d.dll에 따라서도 달라질 수 있습니다.

예를 들어, 여러분들이 test.dll 라이브러리를 만들었고 다음의 API를 export하고 있다고 가정해 보겠습니다.

__declspec(dllexport) int *AllocIntegerData(int len)
{
    int *pData = (int *)malloc(len);
    return pData;
}

그리고 위의 DLL을 링크한 test.exe 프로젝트에서 다음과 같이 사용할 수 있을 것입니다.

void main()
{
    int *pData = AllocIntegerData(100);
    free(pData);
}

이 상황에서, test.dll을 컴파일할 때 링크한 CRT 라이브러리가 msvcr100.dll이고, test.exe 프로젝트를 컴파일할 때 링크한 CRT 라이브러리가 msvcr120.dll이라면 어떻게 될까요?

이것은, 운에 따라 다릅니다. 만약 msvcr100.dll CRT 라이브러리에서 malloc으로 관리되는 내부 부가 데이터가 12바이트이고, msvcr120.dll에서는 16바이트라고 가정해 보겠습니다. 그럼, test.dll에서 AllocIntegerData는 +12바이트가 되는 영역을 할당받았는데 test.exe에서는 그 데이터를 +16바이트만큼 해제해버리는 결과를 낳게 됩니다. (사실, MSVCRT의 경우 이렇게까지 하위 호환성이 없지는 않습니다.)

이런 문제 때문에, DLL 내에서 할당받은 메모리는 반드시 그 DLL에서 해제를 하도록 권장하는 것입니다. (또는, DLL을 사용하는 측이 언제나 C/C++ 언어라고 가정할 수 없습니다.) 즉, 다음과 같이 test.dll에서 해제까지 담당하는 API를 제공하고,

__declspec(dllexport) void FreeIntegerData(int *pBuf)
{
    free(pBuf);
}

DLL을 사용하는 다른 프로젝트에서는 이렇게 Alloc/Free를 맞춰서 사용해 주면 됩니다.

void main()
{
    int *pData = AllocIntegerData(100);
    FreeIntegerData(pData);
}

결코 잊지 말아야 할 것!

"DLL의 경우, 메모리를 할당받는 API를 export시켰다면, 반드시 그 메모리를 해제하는 API까지 제공한다."

그렇다면 new 할당에 대한 의미도 풀립니다. new는 C++에서 제공하는 연산자로 대개의 경우 기본 할당은 내부적으로 malloc을 이용하지만 이는 결코 장담할 수 없습니다. 일례로 Visual C++의 경우 디버그 모드에서는 new가 malloc이 아닌 malloc_dbg를 사용하도록 하고 있으며 심지어 new 연산자 자체가 오버로딩이 가능하므로 사용자가 임의로 다른 메모리 할당 함수를 사용하는 것도 가능합니다.

따라서 말할 필요도 없이, new로 할당받은 메모리를 반환하는 DLL이 있다면 반드시 그것을 (타입에 맞는 delete로) 해제하는 함수도 제공해야 합니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 10/25/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 16  17  [18]  19  20  21  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13198정성태12/18/20224806.NET Framework: 2080. C# - Microsoft.XmlSerializer.Generator 처리 없이 XmlSerializer 생성자를 예외 없이 사용하고 싶다면?파일 다운로드1
13197정성태12/17/20224607.NET Framework: 2079. .NET Core/5+ 환경에서 XmlSerializer 사용 시 System.IO.FileNotFoundException 예외 발생하는 경우파일 다운로드1
13196정성태12/16/20224816.NET Framework: 2078. .NET Core/5+를 위한 SGen(Microsoft.XmlSerializer.Generator) 사용법
13195정성태12/15/20225298개발 환경 구성: 655. docker - bridge 네트워크 모드에서 컨테이너 간 통신 시 --link 옵션 권장 이유
13194정성태12/14/20225408오류 유형: 833. warning C4747: Calling managed 'DllMain': Managed code may not be run under loader lock파일 다운로드1
13193정성태12/14/20225507오류 유형: 832. error C7681: two-phase name lookup is not supported for C++/CLI or C++/CX; use /Zc:twoPhase-
13192정성태12/13/20225562Linux: 55. 리눅스 - bash shell에서 실수 연산
13191정성태12/11/20226464.NET Framework: 2077. C# - 직접 만들어 보는 SynchronizationContext파일 다운로드1
13190정성태12/9/20226952.NET Framework: 2076. C# - SynchronizationContext 기본 사용법파일 다운로드1
13189정성태12/9/20227824오류 유형: 831. Visual Studio - Windows Forms 디자이너의 도구 상자에 컨트롤이 보이지 않는 문제
13188정성태12/9/20226407.NET Framework: 2075. C# - 직접 만들어 보는 TaskScheduler 실습 (SingleThreadTaskScheduler)파일 다운로드1
13187정성태12/8/20226346개발 환경 구성: 654. openssl - CA로부터 인증받은 새로운 인증서를 생성하는 방법 (2)
13186정성태12/6/20224896오류 유형: 831. The framework 'Microsoft.AspNetCore.App', version '...' was not found.
13185정성태12/6/20225862개발 환경 구성: 653. Windows 환경에서의 Hello World x64 어셈블리 예제 (NASM 버전)
13184정성태12/5/20225055개발 환경 구성: 652. ml64.exe와 link.exe x64 실행 환경 구성
13183정성태12/4/20225070오류 유형: 830. MASM + CRT 함수를 사용하는 경우 발생하는 컴파일 오류 정리
13182정성태12/4/20225796Windows: 217. Windows 환경에서의 Hello World x64 어셈블리 예제 (MASM 버전)
13181정성태12/3/20225190Linux: 54. 리눅스/WSL - hello world 어셈블리 코드 x86/x64 (nasm)
13180정성태12/2/20225268.NET Framework: 2074. C# - 스택 메모리에 대한 여유 공간 확인하는 방법파일 다운로드1
13179정성태12/2/20224609Windows: 216. Windows 11 - 22H2 업데이트 이후 Terminal 대신 cmd 창이 뜨는 경우
13178정성태12/1/20225146Windows: 215. Win32 API 금지된 함수 - IsBadXxxPtr 유의 함수들이 안전하지 않은 이유파일 다운로드1
13177정성태11/30/20225838오류 유형: 829. uwsgi 설치 시 fatal error: Python.h: No such file or directory
13176정성태11/29/20224702오류 유형: 828. gunicorn - ModuleNotFoundError: No module named 'flask'
13175정성태11/29/20226535오류 유형: 827. Python - ImportError: cannot import name 'html5lib' from 'pip._vendor'
13174정성태11/28/20224968.NET Framework: 2073. C# - VMMap처럼 스택 메모리의 reserve/guard/commit 상태 출력파일 다운로드1
13173정성태11/27/20225729.NET Framework: 2072. 닷넷 응용 프로그램의 스레드 스택 크기 변경
... 16  17  [18]  19  20  21  22  23  24  25  26  27  28  29  30  ...