Bellek özgüleme yordamları karşılaştırması – HeapCreate, HeapAlloc, GlobalAlloc, LocalAlloc, VirtualAlloc, CoTaskMemAlloc, malloc

Kategori Assembly, Belgeler
16.05.2017
2785 Okunma

Esen,
Bu yazımda Assembly ya da C++ kodlarken karşımıza sıklıkla çıkan bellek özgüleme -memory allocation- yordamları hakkında bilgiler ve kaynak kodlar vermeye çalışacağım. MSDN ağında Memory Mamagement Functions – Bellek Yönetim Yordamları adı altındaki bette windows için kullanılacak tüm bellek yordamları listelenmiş. Gönül isterdiki hepsini uzun uzun örneklerle anlatayım ama öyle bir zaman dilimi yok. Ben burada en çok kullanacaklarımızı anlatmaya özen gösteriyorum. Daha çok ayrıntı isteyenler o beti incelesinler.

GlobalAlloc, LocalAlloc, HeapAlloc en sonunda heap-yığıt ‘tan bellek alanı ayırma işlemi yapan ve aralarında ufak bazı farklılıklar olan yordamlardır. 32 bit Windows ile birlikte başlayan GlobalAlloc ve LocalAlloc yordamları özünde HeapAlloc yordamının “wrapper” yani “sarmalayan” yordamlarıdır. Bu şu anlama geliyor GlobalAlloc ve LocalAlloc bellekte ayırma işlemi yaparken aslında HeapAlloc yordamını kullanmaktadır.Farklı yığıt bölüştürücüleri yine farklı mekanizmalar kullanarak belirgin işlevsellik sağladıkları için doğru yordamla bellek alanını serbest bırakmak gerekmektedir. HeapAlloc için HeapFree, LocalAlloc için LocalFree ve GlobalAlloc için GlobalFree kullanılmalıdır.

HeapCreate

Çağıran proses HeapAlloc ile bellek blokları ayırabilsin diye HeapCreate özel bir yığıt-heap nesnesi yaratır. HeapCreate yordamı başarılı bir şekilde çalışırsa yarattığı yığıt-heap ‘in HANDLE değerini döndürür. HeapAlloc’da bu HANDLE değerini kullanarak bellek blokları ayırabilir.

HeapCreate ile heap-yığıt ayırma işlemini pek kullanmıyoruz. Çünkü işletim sistemi zaten bir tane yaratmış oluyor. GetProcessHeap APIsi ile işletim sisteminin yarattığı yığıtın HANDLE değerini döndürüp işlem yapabiliriz.

HANDLE WINAPI HeapCreate(
  _In_ DWORD  flOptions,
  _In_ SIZE_T dwInitialSize,
  _In_ SIZE_T dwMaximumSize
);

flOptions yığıt ayırma seçenekleridir. Bu seçeneklere göre heap yaratılıp ve bundan sonra yapılacak ayırma işlemlerini etkiler. Bu seçenek 0 veya aşağıdaki seçeneklerden birisi olabilir.

dwInitialSize bayt türünde heap-yığıt’ın başlangıç değeridir. dwMaximumSize’dan küçük olmalıdır. NULL değeri alabilir. Eğer null olursa sadece bir sayfa yaratır.
dwMaximumSize bayt türünden heap-yığıtın maximum büyüklüğüdür. Eğer HeapAlloc ya da HeapReAlloc yordamları dwInitialSize’da belirtilen değeri aşarsa sistem dwMaximumSize’da belirtilen değere kadar heap-yığıt boyutunu genişletir.

Bu değer NULL olabilir. Biz de genelde bunu kullanacağız. Böylece bir üst sınır vermemiş oluyoruz. Heap-yığıt büyüyebiliyor. Heap-Yığıt’ın sınırı kullanılabilir/uygun/müsait bellek alanı ile sınırlanmış oluyor. Büyük bellek boyutları ayrılacaksa kesinlikle bu değişkeni 0 yapmalıyız.
Bu değer sıfır değilse bir üst değer vermiş ve heap-yığını kısıtlamış oluyoruz. Bu üst sınırdan daha fazla yığıtımız büyüyemiyor.

Eğer yordam başarısız olursa 0 döndürür. Sonrasında GetLastError yordamını çağırarak ne hatası olduğunu döndürebiliriz.

HeapAlloc

Heap-yığıttan bellek bloğu ayırır. Ayrılan bellek hareketsizdir.

LPVOID WINAPI HeapAlloc(
  _In_ HANDLE hHeap,
  _In_ DWORD  dwFlags,
  _In_ SIZE_T dwBytes
);

hHeap ayıracağımız bellek alanının HANDLE değeri.HeapCreate ve GetProcessHeap tarafından döndürülmüş olabilir.
dwFlags heap-yığıt ayırma seçenek bayraklarıdır. Burada yapılacak seçimler HeapCreate ile yaratılırken yapılan seçimleri geçersiz kılar. Aşağıdaki seçeneklerden biri ya da bir kaçı seçilebilir.

dwBytes bayt türünden ayrılacak yığıt alanı. Eğer hHeap büyüyemeyen bir yığıtın HANDLE değeriyse dwBytes 0x7FFF8’da küçük olmalıdır.

Yordam başarıyla çalıştığında ayrılan bellek bloğunun adresinin göstergeci(pointer) döndürülür.
Yordam başarısız olurda NULL değeri döner. Eğer HEAP_GENERATE_EXCEPTIONS seçilmişse GetExceptionCode makrosunun kullandığı WinBase.h header dosyasından bulunan döüş kodlarını döndürür. Örnek olarak:

Yordam hata verirse SetLastError APIsini çağırmaz, o yüzden hata kodunu GetLastError ile genişletilmiş hata bilgisine ulaşılmaz.

HeapFree

HeapAlloc veya HeapReAlloc ile yığıttan ayrılan bellek alanını serbets bırakır. lpMem ayrılan bellek alanının poinbter-göstergecidir. Bir kere serbest bırakıldıktan sonra içindekiler sonsuza kadar gider geri döndürülemez. Yani aynı pointer birkez daha kullanılmaz. Gerekiyorsa HeapAlloc ile tekrar bellek alanı ayrılır.

BOOL WINAPI HeapFree(
  _In_ HANDLE hHeap,
  _In_ DWORD  dwFlags,
  _In_ LPVOID lpMem
);

hHeap serbest bırakılacak bellek alanının içinde bulunduğu yığıtın HANDLE değeri
dwFlags heap-yığıt serbest bırakma seçenekeleri. NULL serilebilir.
lpMem HeapAlloc veya HeapRealloc tarafından yığıtta ayrılan ve şimdi serbest bırakılmak istenen bellek alanının göstergeci(pointer).
Yordam başarılı olursa dönüş değeri sıfır olmaz.
Yordam başarısız olursa dönen değer 0 olur. GetLastError ile hatayı döndürebiliriz.

HeapDestroy

hHeap HANDLE değeriyle belirtilen yığıt nesnesini yok eder. Parametre olarak verilen HANDLE değerini geçersiz kılar.

BOOL WINAPI HeapDestroy(
  _In_ HANDLE hHeap
);

hHeap yok edilecek yığıt’ın HANDLE değeri. Bu handle yukarıda anlattığım HeapCreate yordamı tarafından döndürülmüştü. GetProcessHeap yordamı tarafından döndürülen yığıt handle’ını bu yordam ile yok etmiyoruz.

GlobalAlloc

Belirtilen bayt büyüklüğü kadar alanı yığıttan ayırır.

HGLOBAL WINAPI GlobalAlloc(
  _In_ UINT   uFlags,
  _In_ SIZE_T dwBytes
);

uFlags bellek ayırma özelikleridir. Eğer 0 seçilirse GMEM_FIXED varsayılan değer olarak alınır.

dwBytes ayrılacak bellek alanının bayt türünden değeri

Yordam başarılı olduğunda yeni ayrılan bellek alanının HANDLE değerini döndürür.
Yordam başarısız olursa dönüş değeri NULL. Genişletilmiş hata bilgisini GetLastError’dan çekebiliriz.

GlobalLock

GlobalAlloc yordamından dönen değer bir belleğin adresi değil global bellek nesnesinin HANDLE değeriydi. Oysa bize içine birşeyler yazabileceğimiz bir bellek alanı gerekiyor. Bunu yapabilmek içinde bir pointer olması lazım. GlobalLock yordamı GlobalAlloc’tan aldığı HANDLE değerini kullanarak bellek bloğunun ilk baytının adresini döndürür. Bizde o pointer-göstergeci kullanarak işlemlerimizi yapabiliriz.

LPVOID WINAPI GlobalLock(
  _In_ HGLOBAL hMem
);

hMem global bellek nesnesinin HANDLE değeri

Yordam başarılıyla çalıştığında bellek alanının ilk baytının adresini döndürür.
Yordam başarısız olursa NULL döndürür.

GlobalUnlock

GMEM_MOVEABLE olarak ayrılan bellek nesnesinin “lock count”da denilen kilit sayacını azaltır. GMEM_FIXED olarak ayrılan bellek nesnesinde bir etkisi yoktur.
Bellek nesnesinin veri yapısı olarak başlangıçta “lock count” değeri 0 olarak atanır. Hareketli bellek nesneleri için bu değişken GlobalLock ile bir artırılır ve GlobalUnlock ile bir azaltılır.
GMEM_FIXED ile açılan bellek nesneleri her zaman 0 “lock count”a sahiptir.

BOOL WINAPI GlobalUnlock(
  _In_ HGLOBAL hMem
);

hMem global bellek nesnesinin HANDLE değeridir.
Yordam çalıştıktan sonra “lock count” azaltılmasına rağmen kilitli kalmışsa, sıfırdan farklı bir dönüş olur.
Yordam başarılı ise dönen değer 0 olur ve GetlastError NO_ERROR döndürür.

GlobalFree

GlobalAlloc ile yaratılan global bellek nesnesini serbest bırakır ve yaratılan HANDLE değerini geçersiz kılar.

HGLOBAL WINAPI GlobalFree(
  _In_ HGLOBAL hMem
);

hMem global bellek nesnesinin HANDLE değeri. GlobalAlloc veya GlobalReAlloc tarafından yaratılmış olabilir. LocalAlloc ile kullanılması uygun değildir.

Yordam başarılı olmuşsa eğer dönen değer 0’dır.

Yordam başarısız olursa global bellek nesnesinin HANDLE değeri döner.GetLastError ile hata bilgisi döndürülebilir.

LocalAlloc

Heap-Yığıttan belirtilen bayt kadar yer ayırır.

HLOCAL WINAPI LocalAlloc(
  _In_ UINT   uFlags,
  _In_ SIZE_T uBytes
);

uFlags bellek ayırma seçeneklerini içerir:

uBytes değişkeni ayrılacak baellek alanını büyüklüğünü gösteriyor, bayt türünden

Yordam başarılı olursa yeni yaratılan bellek nesnesinin HANDLE değeri döner
Yordam başarısız olursa NULL döner.

LocalLock

LocalAlloc ile yaratılan yerel bellek nesnesini kititler ve birşeyler yazdırabileceğimiz bellek bloğunun adresini gösteren pointer-göstergeci döndürür.

LPVOID WINAPI LocalLock(
  _In_ HLOCAL hMem
);

hMem LocalAlloc ile yarattığımız bellek nesnesinin HANDLE değeri

Yordam başarılı olursa bellek alanının ilk baytını döndürür.
Başarısız olursa NULL döndürür.

LocalUnlock

LMEM_MOVEABLE olarak ayrılan bellek nesnesinin “lock count”da denilen kilit sayacını azaltır. LMEM_FIXED olarak ayrılan bellek nesnesinde bir etkisi yoktur.
Bellek nesnesinin veri yapısı olarak başlangıçta “lock count” değeri 0 olarak atanır. Hareketli bellek nesneleri için bu değişken GlobalLock ile bir artırılır ve GlobalUnlock ile bir azaltılır.
GMEM_FIXED ile açılan bellek nesneleri her zaman 0 “lock count”a sahiptir.

BOOL WINAPI LocalUnlock(
  _In_ HLOCAL hMem
);

hMem yerel bellek nesnesinin HANDLE değeri
Yordam başarılı olursa sıfır döndürür ve GetlastError çağrılırsa NO_ERROR döndürürlür. “lock count” azaltılmasına rağmen bellek alanı hala kilitli ise, kapatıp açın. Şaka şaka, dönen değer sıfırdan farklı bir sayı olur. Ne olduğunu anlamak için bu durumda GetLastError çağrılıp hata konuna bakılır.

LocalFree

Belirtilen yerel ballek nesnesini serbest bırakır ve HANDLE değerini geçersiz kılar.

HLOCAL WINAPI LocalFree(
  _In_ HLOCAL hMem
);

hMem yerel bellek nesnesinin HANDLE değeri
Yordam başarılı olursa NULL döndürür.
Yordam başarısız olursa global bellek nesnesinin HANDLE değeri döner.GetLastError ile hata bilgisi döndürülebilir.

Şimdi durup Global ve Local yordamları bir karşılaştıralım. 32-bit windowstan sonra bu iki yordam aslında Heap Yordamlarını sarmalayan iki ayrı yordamdır ve çok benzerdir. MSDN’e göre yeni uygulamalar Heap-Yığıt yerdamlarını kullanmalıdır diyor. Ama istisnalar var. Örneğin bazı windows APIleri çalıştırıldıktan sonra “LocalFree” ile bellek alanını serbet bırakılmasını istiyormuş. Dynamic Data Exchange(DDE), clipboard-pano yordamları ve OLE veri nesneleri hala global yordamları kullanıyor. Durum böyle olunca kullandığımız APIlerin belegelendirmelerini MSDN’den okuyup incelemek gerekiyor.

32bit Windows’un Local-Global ayrımı artık yok. 16bit zamanında önemliydi. O nedenle şu an bu seçimi yapmak tamamen kişiseldir. Artık kullandığınız yordam-APIlerin ne istedikleri. Artık 32 bit ile virtual memory – sanal bellek modeli uygulandığı için hem yerel hem global yordamlar 32bit virtual address-sanal adress döndürürler. Artık “far” ve “near” muhabbet yok.

GlobalAlloc LocalAlloc ve HeapAlloc aynı yığıttan bellek ayırırlar. Bellek ayrılamazsa, yani bir hata ile karşılaşıldığında HeapAlloc yordamını buna göre ayarlayabilirken Global ve Local yordamları ayarlayamayız. LocalDiscard ve GlobalDiscard gibi yordamlarda HANDLE’ı geçersiz hala getirmeden etkisizleştirebilir, sonrada LocalReAlloc ve GlobalReAlloc ile yeniden kullanılır hale getirebiliriz ama Heap yordamında bu özellik yoktur.

VirtualAlloc

VirtualAlloc çağıran prosessin “virtual address space – sanal adres uzayı”nda sayfa bölümlerinin durumlarını ayırır(reserve), işler(commit) ya da değiştirir(change). Bu yordam çağrıldığında belirtilen adres uzayı 0 olarak ayrılır. VirtualAlloc bellek alanı ayırırken bazı ek seçenekler daha sunmaktadır. “Page Granularity” kullandığından dolayı bu yordam daha çok bellek kullanımını ortaya çıkartır.

LPVOID WINAPI VirtualAlloc(
  _In_opt_ LPVOID lpAddress,
  _In_     SIZE_T dwSize,
  _In_     DWORD  flAllocationType,
  _In_     DWORD  flProtect
);

lpAddress bu seçeneğe bağlı bir göstergeçtir. Ayrılacak yerin başlangıç adresini gösterir. Genelde NULL girilir. Böylece sistem bize bir adres ayarlar.
dwSize bayt türünde ayrılacak alanın büyüklüğüdür. Bu parametre de NULL ayarlanırsa diğer bellek sayfasının sınırına kadar olan bölge ayrılır.
flAllocationType parametresi ile ayrılacak bellek türünü belirleriz.

flProtect ile ayrılacak sayfa bölümlerinin nasıl korunacağına dair seçenekler girebiliriz. Ayrıntılı olarak “Bellek Koruma Sabitleri” adresinden bu sabitlere bakabilirsiniz.

/blockquote>
Yordam başarılı olursa dönen değer adres olur.
Yordam başarısız olursa NULL döner.

VirtualLock

Proses’in sanal adresi uzayını fiziksel bellekğe doğru kitler böylece sonraki erişimlerin bir hata ortaya çıkarmasına izin verilmemiş olur. Ufak bir uygulama yazıyorsanız kullanmanıza gerek olmayabilir.

BOOL WINAPI VirtualLock(
  _In_ LPVOID lpAddress,
  _In_ SIZE_T dwSize
);

lpAddress kitlenecek yerin adresini gösterir
dwSize kitlenecek yerin büyüklüğü
Yordam başarılı olursa sıfır olmayan bir değer döndürür
Yordam başarısız olursa sıfır – 0 döndürür.

VirtualUnlock

VirtualLock ile kilitleiğimiz bellek alanı ile işimiz bittiğinde serbest bırakmadan önce kilidi bu yordam ile açıyoruz.

BOOL WINAPI VirtualUnlock(
  _In_ LPVOID lpAddress,
  _In_ SIZE_T dwSize
);

Yordam başarılı olduğunda sıfırdan farklı bir değer döndürüyor.
Yordam başarısız olduğunda dönen değer 0.

VirtualFree

VirtualAlloc ile ayırdığımız bellek alanını serbest bırakır ya da reserv duruma getirir. Veya isterseniz her ikisini birden yapar.

BOOL WINAPI VirtualFree(
  _In_ LPVOID lpAddress,
  _In_ SIZE_T dwSize,
  _In_ DWORD  dwFreeType
);

lpAddress ayırdığımız bellek alanının adresi
dwSize büyüklüğü
dwFreeType seçenkleri ayırdığımız bellek alanına ne yapacağımızı söylüyor:

Yordam başarılı olursa eğer sıfırdan farklı bir değer döner
Yordam başarısız olursa 0 döner.

CoTaskMemAlloc

Windows’ta “COM-aware”, COM nesleri farkındalığı olan yordamlar var. Genelde bu apiler “Co” ön eki ile kendilerini belli ederler. Özellikle C++’da COM nesneleri ile çalışıyor ve ayırdığınız bellek alanlarının açıkça COM nesneleri ile ilişkilendiriyorsanız bu tür yordamlara gereksiniminiz var.
IMalloc::Alloc yordamıyla aynı şekilde bellekte yer ayırır.

LPVOID CoTaskMemAlloc(
  _In_ SIZE_T cb
);

cb bayt türünde ayrılacak bellek bloğu
Eğer yordam başarılı olursa ayrılan bellek bloğunu döndürür.

CoTaskMemFree

CoTaskMemAlloc yordamı tarafından döndürülen bellek alanını serbest bırakır.

void CoTaskMemFree(
  _In_opt_ LPVOID pv
);

pv serbest bırakılacak bellek alanının göstergeci(pointer)
Geri döndürdüğü bir değer yoktur.

malloc

malloc çalışma zamanı bağımlısı olmasına rağmen derleyici ve dilden bağımsızdır. Genelde “portable” taşınabilir olarak ifade edilen bir fonksiyondur. GlobalAlloc ise aksine Windows APIsidir.

void *malloc(  
   size_t size   
); 

size bayt türünden ayrılacak bellek bloğunun büyüklüğü
Yordam başarılı olduğunda bellek bloğunun adresini döndürür. yetersiz alan olduğunda ise 0 döndürür.
Yordam başarısız olursa çarşı karışır. errno gibi global makoları kurar. Mesela – ENOMEM Not enough memory – gibi.

free

malloc ile ayırdığımız bellek bloğunu serbest bırakmaya yarar.

void free(   
   void *memblock   
); 

memblock malloc ile ayırdığımız yeri gösteren göstergeç-pointer

Bu konuya başladığımda yazının bu kadar uzayacağını öngörmemiştim. Evet çok önemli ve derin bir konu olduğunu biliyorum ancak bazı ayrıntılarda öylesine geçilmiyor. Derinlemesin öğrenmek herzaman daha akıllıca bir seçim.
[dm]149[/dm]

Comments of this post

Henüz yorum bulunmuyor!