Programming/Win32 API2008. 11. 19. 11:16
Question
  Win32 API 책(30장메모리 부분)을 보니까 메모리 할당 부분에 malloc와 new가 나오고
가상 메모리 부분에 VirtualAlloc이 나오든데
그럼 malloc하는 부분은 가상 메모리가 아니란 말씀이신가요?
malloc 하는 부분과 VirtualAlloc 하는 부분의 메모리가 다른 부분인가요?

그리고 그 뒤에 힙 부분에 HeapAlloc와 GlobalAlloc, LocalAlloc 가 나오든데
HeapAlloc는 Win32함수이고 다른 두 개는 Win16의 잔재라고 나와 있더군요.
그래서 이 차이점은 알겠는데
제가 듣기론 malloc도 힙 영역에 메모리를 할당한다고 들었는데
그런것 아닙니까? malloc는 힙 아닌 다른 영역에 할당 하나요?
힙 부분에 다른 함수들이 나오니까 다른 영역에 할당 하나 싶어서요.
그리고 HeapAlloc나 GlobalAlloc는 가상 메모리에 할당 하는게 아닌가요?
가상 메모리의 힙 영역에 할당하는 것인가요?

일단 제가 이해 하기로는 모두 실질적인 메모리에 할당 하는 것은 아니고
가상 메모리에 할당하고 이것을 페이징 해서 실제 메모리에 올리고 사용하고
내리고 하는 걸로 알고 있는데 맞나요?
그리고 가상 메모리의 VirtualAlloc가 할당하는 부분과 malloc가 할당하는 영역,
그리고 HeapAlloc가 할당 하는 영역은 다르다.
이런 건가요?
Answer
  가상메모리란 말그대로 실제 메모리가 아닌 가상의 메모리입니다. win32 환경에서
모든 프로세스는 독립적인 4G 공간의 메모리 주소 공간을 가지게 됩니다. 그런데
실제로 4G의 메모리를 탑재하고 있는 컴퓨터는 없으며, 4G * 프로세스의 갯수
만큼의 메모리를 탑재한 컴퓨터는 더더욱 없습니다. 따라서 없는 것을 실제로
눈에 보이는 것처럼 만들기 위해서 몇가지 트릭이 사용됩니다. 가장 큰 트릭이
스와핑과 페이징이죠.

스와핑은 현재 실제 사용중이지 않은 메모리를 디스크로 옮기고, 실제로 필요한
페이지가 없을 경우 디스크로 부터 로딩해서 사용하는 기술입니다. 페이징은 좀
더 복잡한 개념인데, 가상 메모리가 실제 어떠한 물리 메모리로 맵핑 되는지의
정보를 가지고 있고 그것들을 변환하는 기술이라고 보시면 됩니다.

이것과 별개로 힙메모리라는 것이 나오는데, 이것은 프로세스에서 빈번히
사용하는 서로다른 크기의 작은 메모리 할당/해제시의 단편화가 적고 빠르게
되도록 만든 구조의 메모리 입니다. 이러한 메모리가 별도로 존재하는 것은
아니고 소프트웨어적으로 그렇게 관리하는 것을 말합니다. 보통은 특정한 크기의
풀을 만들어 두고 해당 풀의 메모리를 효율적으로 분할해서 쓰는 방식을 많이
사용합니다.

그럼 각각 이것들을 조작하는 API를 살펴보도록 하죠. 가장 저수준의
VirtualAlloc이라는 함수는 실제 가상 메모리를 할당받고 해제하는 함수입니다.
VirtualAlloc함수를 통해서는 실제 메모리 할당 뿐만 아니라 해당 페이지를
예약하는 등의 저수준 제어도 할 수 있습니다.

HeapAlloc이라는 함수는 힙에 메모리를 할당하는 함수입니다. HeapAlloc의 첫번째
인자로 넘어가는 것이 힙 핸들이죠. 힙은 HeapCreate함수를 통해 생성할 수
있으며, win32에서는 프로세스별로 하나의 기본 힙이 제공됩니다.
GetProcessHeap을 통해서 구할 수 있습니다.

malloc, free, new, delete 함수는 위의 API 보다 좀 더 상위의 계층에서 이러한
일들을 하게 됩니다. C 런타임 라이브러리 초기화 코드에서 해당 라이브러리의 힙
관리자 들이 별도의 힙 메모리를 만들고 관리하게 됩니다.

단순하게 이해하기 위해서는 그냥 위의 세가지 종류가 계층 구조라고 생각하면
편합니다. VirtualAlloc이 가장 하부의 계층, 그것을 이용해 HeapAlloc등의 API가
구현되고, 그 상위에 CRT 힙 함수들이 존재하는 것이죠.

실제로 응용 프로그램 계층에서의 대부분의 작업은 CRT 함수로도 충분히
가능합니다. 별도의 자료 구조를 특정 힙에 할당하는 방법을 쓸때에 Heap함수들을
사용하나 그렇게 자주 있는 일도 아니죠. VirtualAlloc을 쓰는 일은 물론 더더욱
없겠죠. 실제로 대용량 메모리를 사용해야 하는 경우에도 Memory mapped file을
통해서 처리하는 것이 일반적입니다.

음... 쭈욱 적고 밑에 질문글을 다시 보니 동문서답한 느낌도 드네용... ㅋㅋㅋ~

결론적으로 말하면 메모리는 하나입니다. 컴퓨터 뜯어보면 하나죠. ㅋㅋ~ 당연히
프로그램에서 사용하는 메모리도 그거 하나입니다. 하지만 편의상 메모리를
나눠서 쓰겠죠. 우리가 용돈을 받았을때 이건 차비, 식비,... 등으로 쪼개
쓰는것과 같다고 보면 됩니다. 컴퓨터도 그런식으로 용도에 맞게 메모리를
효율적으로 사용하기 위해서 가상메모리, 힙, 스택메모리 같은걸로 분리해서
사용합니다~

--
신영진 (Shin, YoungJin)
http://www.jiniya.net


Posted by skensita
Programming/Win32 API2008. 11. 19. 11:13

From Devpia

각 함수의 정의는 다음과 같다.
 
LPVOID HeapAlloc(HANDLE hHeap, DWORD dwFlags, DWORD dwBytes);
HLOCAL LocalAlloc(UINT uFlags, UINT uBytes);
HGLOBAL GlobalAlloc(UINT uFlags, DWORD dwBytes);

셋 다 힙에서 메모리 블록을 할당받는 동일한 기능을 한다.

LocalAlloc과 GalobalAlloc은 Win32에서는 완전히 동일하다. Win16에서는 어플리케이션마다 가지는 Local 힙과 힙 매니저가 가지는 Global 힙이 따로 존재했지만 Win32에서는 따로 존재하지 않는다. 따라서 Win32에서 Local 힙과 Global 힙은 동일하고 함수 자체는 하위 호환을 위해 남아있다.

LocalAlloc과 GlobalAlloc은 동일하므로 LocalAlloc만을 언급하겠다.

HeapAlloc과 LocalAlloc의 차이점을 살펴보자.
첫째로 메모리 블록을 할당하는 힙 자체가 다르다는 것이다. LocalAlloc은 항상 디폴트 힙에서 할당한다. 디폴트 힙은 하나의 어플리케이션이 하나씩 가지는 메인 힙이다. 이는 프로세스가 생성되고 가상 주소 공간이 생겨날 때 같이 생성되는 힙이다. 그 크기는 링크시 실행파일 헤더에 기록이 된다. 링크 옵션으로 그 크기를 변경할 수도 있다. 기본 값은 1MB이다. 반면에 HeapAlloc은 함수의 첫 번째 인자에서 보듯이 임의의 힙에서 할당한다. HeapCreate로 만든 힙일 수도 있고 GetProcessHeap으로 얻는 디폴트 힙일 수도 있다.

둘째로 리턴값을 살펴보면 HeapAlloc은 포인터를 리턴하지만 LocalAlloc은 핸들을 리턴한다. 따라서 LocalAlloc으로 할당받은 메모리 블록을 실제로 사용할 때는 LocalLock 함수로 포인터를 얻어와야 한다. LocalAlloc의 사용이 좀 더 불편하지만 핸들을 사용하므로써 동기화와 같은 기능을 제공한다. 그리고 포인터를 직접 사용하지 않고 포인터를 사용하는 또 다른 (결과적으로) 이유는 다음과 같다.
LocalAlloc의 첫번째 인자는 할당받을 메모리 블록의 특성을 지정하는데, GMEM_MOVEABLE 이라는 특성을 지정할 수 있다. 이는 힙 메모리 단편화 제거를 위해 또는 힙 공간이 부족할 때 힙 관리자가 메모리 블록을 이동할 수 있도록 하게 하는데 이렇게 되면 포인터로 리턴했을 경우 이동된 후 잘못된 참조를 하게 되므로 핸들로서 관리되는 것이다. 실제로 GMEM_MOVEABLE 이 아닌 GMEM_FIXED로 지정하게 되면 리턴값은 그 자체가 포인터이다.

※ 참고로 C run-time library인 malloc은 위의 API 함수들과 같은 기능을 하지만 디폴트 힙이 아닌 자신만의 힙을 사용하고 자신만의 방법으로 힙을 관리한다.

+ VirtualAlloc() 함수는 뭐냐. 이것도 생각해 봐야.

LocalAlloc()과 같은 heap function 은 memory exception 을 가져 올 수
있다는 것과 대용량의 memory 할당에는 적합하지 않다는 것입니다.



=======  From MSDN : "Heap Functions" ============================================
Windows NT 4.0: External factors may cause accesses to heap memory to generate access violations.
One possible cause of an access violation is very limited space in the paging file. Therefore, all
accesses to heap memory should be protected with structured exception handling. This is not
necessary on later versions of Windows.

Windows 95/98/Me: The heap managers are designed for memory blocks smaller than four megabytes. If
you expect your memory blocks to be larger than one or two megabytes, you can avoid significant
performance degradation by using the VirtualAlloc or VirtualAllocEx function instead.

======= From MSDN : "Comparing Memory Allocation Methods" =========================
This topic provides a brief comparison of the following memory allocation methods:

CoTaskMemAlloc
GlobalAlloc
HeapAlloc
LocalAlloc
malloc
new
VirtualAlloc

The following functions are equivalent: GlobalAlloc, LocalAlloc, and HeapAlloc with the handle
returned by the GetProcessHeap function.

The VirtualAlloc function allows you to specify additional options for memory allocation. However,
its allocations use a page granularity, so using VirtualAlloc can result in higher memory usage.

The malloc function has the disadvantage of being run-time dependent. The new operator has the
disadvantage of being compiler dependent and language dependent.

The CoTaskMemAlloc function has the advantage of working well in either C, C++, or Visual Basic.
It is also the only way to share memory in a COM-based application, since MIDL uses CoTaskMemAlloc
and CoTaskMemFree to marshal memory.

GlobalAlloc, LocalAlloc, HeapAlloc 함수들은 궁극적으로 동일한 힙에 메모리를 할당하지만, 약간의 차이를 가지고 있다. HeapAlloc은 메모리가 할당될 수 없으면 예외를 발생시킬 수 있으며, LocalAlloc은 이런 기능이 없다. LocalAlloc은 핸들 할당을 지원함으로써 핸들값을 바꾸지않고 재할당을 통해 기초메모리 (underlying memory) 를 이동시킬 수 있으며, HeapAlloc은 이런 기능이 없다.

각각의 힙 할당자는 서로 다른 기법을 사용해 구현되었기에, 각 함수에 맞게 메모리를 해제해야 한다. 예를 들어, HeapAlloc으로 할당된 메모리는 LocalFree / GlobalFree이 아닌 HeapFree으로 해제되어야 한다.

VirtualAlloc 함수는 메모리 할당에 추가 옵션을 허용한다. 하지만 이 함수는 페이지 입도를 사용하므로, 더 많은 메모리가 소비될 수 있다.

malloc함수는 런타임 의존적, new연산자는 컴파일러/언어 의존적이라는 약점을 가지고 있다.

 

CoTaskMemAlloc함수는 C/C++/Visual Basic 모두에서 잘 작동하는 이점을 가지고 있다. 또한 MIDL (Microsoft Interface Definition Language) 이 메모리를 마샬링*하는데 CoTaskMemAlloc / CoTaskMemFree를 사용하므로, COM 기반 애플리케이션에서 메모리를 공유하는 유일한 방법이다.

 

*마샬링(marshalling) : 하나 이상의 프로그램 또는 연속적이지 않은 저장공간으로부터 데이터를 모은 다음, 이들을 메시지 버퍼에 집어넣고 특정 수신기나 프로그래밍 인터페이스에 맞도록 그 데이터를 조직화하거나, 미래 정해진 다른 형식으로 변환하는 과정.

주로 한 언어로 작성된 프로그램의 출력 매개변수를 다른 언어로 작성된 프로그램의 입력으로 전달해야 하는 경우 필요하다.

 

 VirtualAlloc은 힙을 사용하지는 않습니다.
그리고 힙처럼 연속된 메모리로 제한되지는 않으며
64KB의 조각들로 할당합니다...
그리고 또 하나...
새로운 사실인데...
new가 메모리 할당에 실패했는데 VirtualAlloc함수가 성공하는 경우가 있다는 군요...

VirtualAlloc이라는 함수가 하드에 잡아준다 이건 절대 아닙니다.
사람들이 가상메모리하면 바로 하드 스와핑을 떠올리시는데 그건 아닙니다.
Virtual* 메모리 API는 Win32에서 가장 원시적인(?) 함수라고 보시면됩니다.
(내가 쓸 메모리는 항상 램에 있어야한다 등등은 AWE라는 메모리 기술로
조절할 수 있습니다)

VirtualAlloc은 큰 메모리를 잡을 때 많이 씁니다.
왜냐면 얘들은 allocation granularity와 page size에 맞춰서
까다롭게 commit, decommit을 해줘야하기 때문이죠.

반면 HeapAlloc함수들은 이런 메모리 바운더리 및 얼라인먼트에대한
부담없이 언제든지 원하는 메모리를 뽑아 쓸 수 있도록 설계된 것이죠.
(결국 HeapAlloc도 VirtualAlloc으로 commit된 메모리에서 나오는 것이죠)
new, malloc 등 모든 함수들은 결국 HeapAlloc으로 귀결된답니다.

아무래도 메모리의 경계를 잘못 맞추어서 겪으신 어려움인것 같군요.
priority낮은 쓰레드가 뒤에서 돌면서 가비지 콜렉터 역할을 하며
페이지크기에 맞게 남은 페이지를 decommit해야하는걸로 알고 있습니다.
여튼, 속도는 잘나오겠지만 쓰는 법이 약간 까다롭죠.

결론은 작은 메모리를 빈번하게 할당, 해지해야할 땐
Heap메모리를 써야한다는 것, 큰 메모리를 비교적 적은 횟수에
쓰실 때엔 Virtual메모리를 쓰는게 정석인 듯 하옵니다..

윈도우 메모리 구조는 Programming Apps for Windows란
책에 가장 자세히 설명되어 있습니다^^

Posted by skensita