Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 46  47  48  49  50  51  52  53  54  55  56  57  [58]  59  60  ...
NoWriterDateCnt.TitleFile(s)
12174정성태3/7/202010113개발 환경 구성: 476. DockerDesktopVM의 파일 시스템 접근 [3]
12173정성태3/7/202011097개발 환경 구성: 475. docker - SQL Server 2019 컨테이너 실행 [1]
12172정성태3/7/202015983개발 환경 구성: 474. docker - container에서 root 권한 명령어 실행(sudo)
12171정성태3/6/202010909VS.NET IDE: 143. Visual Studio - ASP.NET Core Web Application의 "Enable Docker Support" 옵션으로 달라지는 점 [1]
12170정성태3/6/20209549오류 유형: 599. "Docker Desktop is switching..." 메시지와 DockerDesktopVM CPU 소비 현상
12169정성태3/5/202011574개발 환경 구성: 473. Windows nanoserver에 대한 docker pull의 태그 사용 [1]
12168정성태3/5/202012260개발 환경 구성: 472. 윈도우 환경에서의 dockerd.exe("Docker Engine" 서비스)가 Linux의 것과 다른 점
12167정성태3/5/202011488개발 환경 구성: 471. C# - 닷넷 응용 프로그램에서 DB2 Express-C 데이터베이스 사용 (3) - ibmcom/db2express-c 컨테이너 사용
12166정성태3/4/202011158개발 환경 구성: 470. Windows Server 컨테이너 - DockerMsftProvider 모듈을 이용한 docker 설치
12165정성태3/2/202010834.NET Framework: 900. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 네 번째 이야기(Monitor.Enter 후킹)파일 다운로드1
12164정성태2/29/202011715오류 유형: 598. Surface Pro 6 - Windows Hello Face Software Device가 인식이 안 되는 문제
12163정성태2/27/202010130.NET Framework: 899. 익명 함수를 가리키는 delegate 필드에 대한 직렬화 문제
12162정성태2/26/202012898디버깅 기술: 166. C#에서 만든 COM 객체를 C/C++로 P/Invoke Interop 시 메모리 누수(Memory Leak) 발생 [6]파일 다운로드2
12161정성태2/26/20209558오류 유형: 597. manifest - The value "x64" of attribute "processorArchitecture" in element "assemblyIdentity" is invalid.
12160정성태2/26/202010240개발 환경 구성: 469. Reg-free COM 개체 사용을 위한 manifest 파일 생성 도구 - COMRegFreeManifest
12159정성태2/26/20208446오류 유형: 596. Visual Studio - The project needs to include ATL support
12158정성태2/25/202010255디버깅 기술: 165. C# - Marshal.GetIUnknownForObject/GetIDispatchForObject 사용 시 메모리 누수(Memory Leak) 발생파일 다운로드1
12157정성태2/25/202010141디버깅 기술: 164. C# - Marshal.GetNativeVariantForObject 사용 시 메모리 누수(Memory Leak) 발생 및 해결 방법파일 다운로드1
12156정성태2/25/20209464오류 유형: 595. LINK : warning LNK4098: defaultlib 'nafxcw.lib' conflicts with use of other libs; use /NODEFAULTLIB:library
12155정성태2/25/20208798오류 유형: 594. Warning NU1701 - This package may not be fully compatible with your project
12154정성태2/25/20208643오류 유형: 593. warning LNK4070: /OUT:... directive in .EXP differs from output filename
12153정성태2/23/202011316.NET Framework: 898. Trampoline을 이용한 후킹의 한계파일 다운로드1
12152정성태2/23/202011033.NET Framework: 897. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 세 번째 이야기(Trampoline 후킹)파일 다운로드1
12151정성태2/22/202011556.NET Framework: 896. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법 - 두 번째 이야기 (원본 함수 호출)파일 다운로드1
12150정성태2/21/202011418.NET Framework: 895. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법 [1]파일 다운로드1
12149정성태2/20/202011184.NET Framework: 894. eBEST C# XingAPI 래퍼 - 연속 조회 처리 방법 [1]
... 46  47  48  49  50  51  52  53  54  55  56  57  [58]  59  60  ...