http://www.delmadang.com/community/bbs_view.asp?bbsNo=3&bbsCat=0&st=&keyword=&indx=413800&keyword1=&keyword2=&page=1
---------------------------------------------------------------------------------
//http://www.codexterity.com/memmgr.htm
//위 글을 날림 번역한 것입니다. 나은 번역 있으시면은 덧글로 부탁합니다.
< 델파이에서의 메모리할당과 DLL >
마법사로 DLL을 만들면 이런 주석이 달립니다.
{중요: ShareMem을 uses절 맨 처음에 달아라. unit이랑 Project-View Sorce 모두에 그렇게 해야 한다.
strings를 파라미터로 전달하거나 결과값으로 리턴하는 경우에 말이다.
record나 class안의 어디에 처박혀 있든, strings가 있다면 효과를 볼 것이다.
SharMem은 공유메모리 관리자인 BORLNDMM.DLL의 interface unit이다. 이 DLL은 당신의 DLL과 같이 배포돼야 한다.
이런 방식이 싫다면, string 정보를 PChar이나 ShortString으로 전달해라.}
왜 이런 주의사항이 필요할까요? 그 이유는 델파이가 메모리를 할당하는 방식에 뿌리를 두고 있습니다.
Windows에는 메모리를 할당할 때 쓰로록 네이티브 루틴들이 준비돼 있습니다. VirtualAlloc, HeapAlloc, GlobalAlloc, LocalAlloc 등이죠.
델파이는 나름의 할당원칙을 지키는데요. 뭐, 더 정확히 말하자면 보조할당기를 쓴다고 할 수 있습니다.
델파이에서는 이런 식입니다. 일단 heap을 부르고요(priority heap아닙니다. 혼동하지 마시길). C/C++에서는 free store라고 것입니다.
보조할당기가 하는 일은 모든 동적 메모리를 할당하는 겁니다.
프로그래머가 명시적으로 잡아주었던 raw memory에서부터,
컴파일러에 의해 암시적으로 할당된 메모리로의
할당이죠.
strings, 동적배열, 객체 등을 생성할 때 말입니다.
사실 개발자들 중에 아래 문장을 적으면서, 메모리가 암시적으로 할당되고 있다고 느끼는 사람은 적습니다.
var
s:string;
...
s:=s+'abc';
델파이 유저들에게 친숙한 동적 메모리할당 함수들은 GetMem, FreeMem, New, Dispose 이런 것들입니다.
하지만, 사실은 단순해 보이는 동작들에서도 델파이는 heap 메모리를 할당해 쓰고 있습니다.
* Object creation using a constructor
* Long string variables, and operations on them
* Operations on short string variables
* Creation and resizing of dynamic arrays
* String/array values in variants
* Explicit allocation using GetMem()/FreeMem(), New() and Dispose()
이런 데서 말입니다.
델파이에서, 모두 객체는 heap에 !삽!니!다!(//"live"로 강조하고 있습니다.)
이는 Java나 C#(Java/Delphi의 영향을 부정하고 있죠)과 비슷합니다.
하지만, C++다릅니다. C++에서 객체들은 stack에도 heap에도 심지어는 data segment에도 삽니다.
16bit windows programming에 친숙한 개발자들은 궁금해 할지도 모르겠군요.
어째서 델파이는 Windows의 heap을 사용하지 않는지 말입니다.
HeapCreat(), HeapAlloc(), HeapFree() 같은 것을 쓰면 되는데 말입니다.
이도저도 아니면 VirtualAlloc(), VirtualFree()를 쓰면 되지 않을까요.
델파이가 이러는 단순한 이유는 speed입니다.
Windows heap functions, 이것들은 Delphi의 native allocation에 비하면 매우 느립니다.// 앞서 말한 Heap...()들
virtual memory allocations, 이것들도 느리기는 마찬가지 입니다. //앞서 말한 Virtual...()들
앞의 것들이 느린 이유는 그저 '아주 많은 조그만 조각들'에 맞도록 디자인한 것이 아니라서 그런 겁니다.
애초에 heap이라는 것이 비교적 큰 덩어리를 위한 공간이니까요.
//Stack은 미리 자리를 차지하기 때문에 비교적 작고 분명한 것을 쌓는데 쓰고, Heap은 상대적으로 큰 것들을 쌓는데 쓴다고 배운듯...
어쨌거나,
하위에 하위를 이어 할당해야하는 상황이어서 커다란 영역이 필요하면,
suballocator는 virtaul memory function들을 최대한 호출합니다.
memory manager code는 System.pas, GetMem.inc에 들어 있습니다. 당연히 모든 어플리케이션에 statically linked됩니다.
이렇게 해도 보통 때에는 아무런 문제가 되지 않습니다.
델파이로 제작한 DLL을 쓰고 있는 어플리케이션에서는 다음과 같이 예측할 수 있습니다.
사실 DLL은 별개로 컴파일된 어플리케이션이라는 점, 그러니까 자체적으로 memory manager의 사본을 받는다는 점,
그러니까 별개의 heap이 만들어 진다는 점(//원문은 'heap이 분리된다'입니다).
이 점이 가장 중요한 점입니다.
각각 별개의 어플리케이션이(exe와 exe뿐만 아니라 exe와 dll 사이라도),
각자 자기의 heap을 관리하게 됩니다.
이 두 heap의 존재를 인식하지 못하고 실수로 메모리를 관리하게 되면, 연달아 문제가 됩니다.
heap이 뭐야?
heap이 낯설고 델파이에서 어떤 식으로 쓰이는 지 모르는 분들을 위해서,
heap은 메모리의 한 영역입니다. 여기에 동적 할당된 메모리(내용)가 저장됩니다.
C든, C++든, Delphi든, C#이든, 모든 structured language에서 프로그래머는 두가지의 메모리를 씁니다.
static, dynamic //정적, 동적
기본 데이터형은 static입니다. 컴파일 할 때 부터 요구사항이 정해져 있습니다. //크기를 압니다.
integer, 열거형, record, 정적 배열, 이런 것들 정적으로 할당됩니다.(staticall allocated)
C에서 모든 자료형은 정적입니다.
그러니까 동적으로 메모리를 할당하고 싶다면, 프로그래머는 명시적으로 malloc()같은 메모리 할당 루틴을 써야 합니다.
한편, 동적 메모리는 run-time에 그 크기가 재조정됩니다.
예를 들자면, Delphi의 long strings, 클래스, 동적배열 등이 있습니다.
VisualBasic에서는, 많은 자료형이 동적으로 할당됩니다. 그중에 대표적인 것이 variant와 동적 배열입니다.
요약하자면, 어떤 데이터 형이든 run-time에 크기가 변할 가능성을 열어두려면, 동적으로 할당되어야 합니다.
컴파일러의 입장에서 보자면, 이 두가지 형태의 메모리는 굉장히 다릅니다.
이 두 친구는 어플리케이션의 메모리 영역중에서도 엄격하게 분리된 다른 구역에 삽니다.
메모리라는 것을 이렇게 둘로 나누어 생각하는 것은 현대 프로그래밍에 깊이 배어 있습니다.//색이 든 천으로 비유
깊게는 OS까지, 아래로는 하드웨어 자체에까지 말입니다.
이래서 x86 family같은 많은 chip들이 explicit data와 stack segments를 분리하여 지원합니다.
자동생성되는 주석문으로 이야기를 시작했는데요.
문자열 정보를 넘기려거든 PChar나 ShortString을 쓰라고 했잖아요
그건 아마도 PChar나 ShortString을 쓰면 문제가 없겠거니 생각하게 만듭니다.
하지만, 매우 잘못된 방향으로 이끄는 것이라고 봅니다.
애 사탕주듯 해서, 개발자들에게 잘못된 보안 감각에 빠져들게 한다고 봅니다.//안정성을 걱정하는 듯
(더 자세한 정보를 보시려면 ShortStrings and Chars라는 글을 보세요.)//원문에 링크 걸려 있음.
자 생각해 봅시다:
In DLL:
function GetStringList: TStringList;
begin
Result := TStringList.Create;
Result.Add( 'foo' );
end;
In EXE:
var obj: TStringList;
...
obj := GetStringList;
// do something
obj.Free; // may corrupt DLL heap; may free >1 blocks
// DLL의 heap을 더럽히고 있습니다. 아마도 한개 이상의 block을 free합니다.
////이부분을 잘 이해하고 번역해야 하는데 그렇지 못한 점 죄송합니다. 아래는 추측입니다.
////EXE의 heap에 메모리를 할당하고, free한다고 생각하겠지만,
////할당하지도 않았던, DLL의 heap에서도 free시도
EXE가 객체에 뭘 하느냐에 달렸지만, 아마도 양쪽 heap을 모두 더럽힐 겁입니다.
자, 봅시다. 델파이6같은 경우에, a모듈의 heap에서부터 b모듈의 메모리를 해제하려고 하는 경우에,
자체로는 heap을 더럽히는 걸로 보이지 않습니다.
heap manager는 free memory block을 linked-list에 보관합니다.
그리고, 삭제가 일어나면 가까운 두 free block을 병합하려고 합니다.
"Invalid pointer operation" exception은
a모듈이 b모듈의 free list에서 마지막으로 할당됐던 block을 해제하려고 할 때에만 일어납니다.
// aModul Free -> bModule.Items[theLast].Free
invalid element이므로 인식하는데 실패하고는,
그 free block을
marker element라고 생각되는 것(혹은 그저 garbage일 수도 있는 것)과 병합하려고 합니다.
이게 에러를 일으킵니다.
에러는 이 경우에만 일어나지만, heap manager의 실행에 있어서는 이런식으로 그치지 않습니다.
나중 버전에서는 heap이 더럽혀지는 일이 아무 곳에서나 일어날 수도 있습니다.
//번역하고도 무슨 소린지 원????? 그래도 하던 것이므로 계속합니다.
위의 코드를 고쳐봅시다.
In DLL:
function GetPChar: PChar;
begin
Result := StrAlloc( 13 );
StrCopy( Result, 'Hello World!' );
end;
procedure FreePChar( p: PChar );
begin
StrDispose( p );
end;
In EXE:
var p: PChar;
...
p := GetPChar;
// do something
FreePChar( p ); // heap-friendly free
TStringList버전은 교정할 수가 없습니다.
EXE의 heap에 생성되었던 strings는 TStringList의 destructor에 의해 free 되어야 하기 때문입니다.
EXE의 heap이 더럽혀집니다.
그럼 해결책은 무엇일까요? 몇가지 방법이 있습니다:
- Sharemem.pas/Borlndmm.dll을 쓰고, 보통처럼 한다:
가장 간단한 방법으로 보입니다. 특별히 주의할 일도 없습니다.
하지만 치러야할 댓가는 있습니다:
모든 할당은 Borlndmm.dll을 경유합니다. 통상적인 할당보다 2~8배 느리겠군요.
이 속도저하는 모든 할당에 적용됩니다.
DLL안에서의 연산에만이 아니라, 문자열연산 객체연산 등등 모두 속도저하됩니다.
어플리케이션 배포할 때 Borlndmm.dll도 같이 배포해야 합니다.
- 모듈간에는 동적 데이터를 절대로 주고 받지 않는다:
어려운 일입니다. 델파이 타입 시스템에 대한 깊은 이해를 요구합니다.
여러 개발자와 함께라면 강요하기가 특히 힘들겁니다.
- DLL이 다른 언어로 쓰인 것처럼 취급하고 API-style로 관리한다:
이것은 다소 그럴듯합니다.
이 경우에, 만약 프로젝트에 다양한 언어가 쓰였다면,
DLL을 교차하는델파이만의 construct를 쓰지않기로 한 것만으로도 더이상 복잡해지지는 않겠습니다.
단점은 많은 Delphi-contruct를 쓰지 못합니다.(객체, 문자열, 동적배열 등등)
- 자신만의 API를 운영한다:
DLL간 클래스나 문자열을 던질 때에는 자신만의 API 만들어 사용합니다.
예를 들자면, 자신의 객체를 생성하고 해제할 GetMyObejct/FreeMyObject같은 것을 만들어 씁니다.
한쪽의 heap만을 쓰도록 만들어야 합니다. 이 의제된 객체에 heap operation을 수행해선 안된다는 것을 기억해야 합니다.
당연한 얘기지만, 이것은 의존적인 프로세스입니다. 원칙이 있어야 합니다.
복잡해지다가 심해진다 싶으면 세 번째 방법으로 하는 것이 낫습니다.
똑같이 복잡하지만, 세번째 방법은 다른 언어에서도 쓰일 수 있다는 장점이 하나 더 있습니다.
- DLL 내부적으로 COM objects를 씁니다:
극단적인 방법입니다. 그저 DLL간의 통신을 위해서만 COM을 씁니다.
퍼포먼스가 떨어지고, 많은 노력을 해야 합니다.
하지만 더 간단한 방법이 있습니다. FastSharemem unit을 사용하는 것입니다.
차선책이기는 합니다만, unit 하나만 추가하면 되므로 사용하기 쉽고, 무엇보다 퍼포먼스가 떨어지지 않습니다.
DLL을 같이 배포해야 할 일도 없습니다.
//번역은 여기까지입니다. heap에 대한 내용전달에 힘썼습니다만, 실력부족입니다.
//Unit하나를 추천하면서 글이 끝나는데, 이 유닛에 대해 모릅니다. 저는 그저 heap에 관심이 있었습니다. ;)
---------------------------------------------------------------------------------
감사합니다...