Microsoft MVP성태의 닷넷 이야기
VC++: 107. VirtualAlloc, HeapAlloc, GlobalAlloc, LocalAlloc, malloc, new의 차이점 [링크 복사], [링크+제목 복사],
조회: 14840
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 31  32  33  34  35  36  37  [38]  39  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
12686정성태6/21/202110070Java: 22. Azure - 자바(Java)로 만드는 Web App Service - Java SE (Embedded Web Server) 호스팅
12685정성태6/21/202110319Java: 21. Azure Web App Service에 배포된 Java 프로세스의 메모리 및 힙(Heap) 덤프 뜨는 방법
12684정성태6/19/20218775오류 유형: 728. Visual Studio 2022부터 DTE.get_Properties 속성 접근 시 System.MissingMethodException 예외 발생
12683정성태6/18/202110256VS.NET IDE: 166. Visual Studio 2022 - Windows Forms 프로젝트의 x86 DLL 컨트롤이 Designer에서 오류가 발생하는 문제 [1]파일 다운로드1
12682정성태6/18/20217926VS.NET IDE: 165. Visual Studio 2022를 위한 Extension 마이그레이션
12681정성태6/18/20217263오류 유형: 727. .NET 2.0 ~ 3.5 + x64 환경에서 System.EnterpriseServices 참조 시 CS8012 경고
12680정성태6/18/20218410오류 유형: 726. python2.7.exe 실행 시 0xc000007b 오류
12679정성태6/18/20218963COM 개체 관련: 23. CoInitializeSecurity의 전역 설정을 재정의하는 CoSetProxyBlanket 함수 사용법파일 다운로드1
12678정성태6/17/20218162.NET Framework: 1072. C# - CoCreateInstance 관련 Inteop 오류 정리파일 다운로드1
12677정성태6/17/20219701VC++: 144. 역공학을 통한 lxssmanager.dll의 ILxssSession 사용법 분석파일 다운로드1
12676정성태6/16/20219713VC++: 143. ionescu007/lxss github repo에 공개된 lxssmanager.dll의 CLSID_LxssUserSession/IID_ILxssSession 사용법파일 다운로드1
12675정성태6/16/20217766Java: 20. maven package 명령어 결과물로 (war가 아닌) jar 생성 방법
12674정성태6/15/20218573VC++: 142. DEFINE_GUID 사용법
12673정성태6/15/20219720Java: 19. IntelliJ - 자바(Java)로 만드는 Web App을 Tomcat에서 실행하는 방법
12672정성태6/15/202110844오류 유형: 725. IntelliJ에서 Java webapp 실행 시 "Address localhost:1099 is already in use" 오류
12671정성태6/15/202117593오류 유형: 724. Tomcat 실행 시 Failed to initialize connector [Connector[HTTP/1.1-8080]] 오류
12670정성태6/13/20219092.NET Framework: 1071. DLL Surrogate를 이용한 Out-of-process COM 개체에서의 CoInitializeSecurity 문제파일 다운로드1
12669정성태6/11/20219070.NET Framework: 1070. 사용자 정의 GetHashCode 메서드 구현은 C# 9.0의 record 또는 리팩터링에 맡기세요.
12668정성태6/11/202110831.NET Framework: 1069. C# - DLL Surrogate를 이용한 Out-of-process COM 개체 제작파일 다운로드2
12667정성태6/10/20219468.NET Framework: 1068. COM+ 서버 응용 프로그램을 이용해 CoInitializeSecurity 제약 해결파일 다운로드1
12666정성태6/10/20218037.NET Framework: 1067. 별도 DLL에 포함된 타입을 STAThread Main 메서드에서 사용하는 경우 CoInitializeSecurity 자동 호출파일 다운로드1
12665정성태6/9/20219323.NET Framework: 1066. Wslhub.Sdk 사용으로 알아보는 CoInitializeSecurity 사용 제약파일 다운로드1
12664정성태6/9/20217619오류 유형: 723. COM+ PIA 참조 시 "This operation failed because the QueryInterface call on the COM component" 오류
12663정성태6/9/20219153.NET Framework: 1065. Windows Forms - 속성 창의 디자인 설정 지원: 문자열 목록 내에서 항목을 선택하는 TypeConverter 제작파일 다운로드1
12662정성태6/8/20218276.NET Framework: 1064. C# COM 개체를 PIA(Primary Interop Assembly)로써 "Embed Interop Types" 참조하는 방법파일 다운로드1
12661정성태6/4/202118961.NET Framework: 1063. C# - MQTT를 이용한 클라이언트/서버(Broker) 통신 예제 [4]파일 다운로드1
... 31  32  33  34  35  36  37  [38]  39  40  41  42  43  44  45  ...