Istnieje wiele metod, aby przydzielić pamięci w środowisku Windows, takie jak VirtualAlloc
, HeapAlloc
, malloc
, new
.
Jaka jest więc różnica między nimi?
Odpowiedzi:
Każdy interfejs API jest przeznaczony do różnych zastosowań. Każdy z nich wymaga również użycia prawidłowej funkcji zwalniania / zwalniania, gdy skończysz z pamięcią.
Niskopoziomowy interfejs API systemu Windows, który zapewnia wiele opcji, ale jest przydatny głównie dla osób w dość specyficznych sytuacjach. Można przydzielić pamięć tylko w (edytuj: nie 4KB) większych fragmentach. Są sytuacje, w których tego potrzebujesz, ale będziesz wiedział, że jesteś w jednej z takich sytuacji. Jednym z najczęstszych jest udostępnianie pamięci bezpośrednio innemu procesowi. Nie używaj go do alokacji pamięci ogólnego przeznaczenia. Służy VirtualFree
do zwalniania przydziału.
Alokuje pamięć o dowolnej wielkości, a nie w dużych porcjach VirtualAlloc
. HeapAlloc
wie, kiedy musi zadzwonić, VirtualAlloc
i robi to automatycznie. Podobnie jak malloc
, ale jest tylko dla systemu Windows i zapewnia kilka dodatkowych opcji. Nadaje się do przydzielania ogólnych fragmentów pamięci. Niektóre interfejsy API systemu Windows mogą wymagać użycia tej funkcji w celu przydzielenia przekazywanej im pamięci lub użycia jej elementu towarzyszącego w HeapFree
celu zwolnienia pamięci, którą zwracają.
Sposób przydzielania pamięci w języku C. Preferuj to, jeśli piszesz w C, a nie C ++ i chcesz, aby Twój kod działał np. Również na komputerach z systemem Unix lub ktoś konkretnie mówi, że musisz go używać. Nie inicjalizuje pamięci. Nadaje się do przydzielania ogólnych fragmentów pamięci, takich jak HeapAlloc
. Prosty interfejs API. Służy free
do zwalniania przydziału. malloc
Wywołania Visual C ++ HeapAlloc
.
Sposób przydzielania pamięci w języku C ++. Wolisz to, jeśli piszesz w C ++. Umieszcza również obiekt lub obiekty w przydzielonej pamięci. Służy delete
do zwalniania alokacji (lub delete[]
dla tablic). Visual Studio new
wywołuje HeapAlloc
, a następnie może inicjalizuje obiekty, w zależności od tego, jak to nazwiesz.
W najnowszych standardach C ++ (C ++ 11 i nowszych), jeśli musisz ręcznie używać delete
, robisz to źle i zamiast tego powinieneś użyć inteligentnego wskaźnika, takiego jak unique_ptr
. Począwszy od C ++ 14 można to samo powiedzieć new
(zastąpione funkcjami takimi jak make_unique()
).
Istnieje również kilka innych podobnych funkcji, takich jak SysAllocString
ta, z której możesz zostać poinformowany, że musisz użyć w określonych okolicznościach.
Bardzo ważne jest, aby zrozumieć różnicę między interfejsami API alokacji pamięci (w systemie Windows), jeśli planujesz używać języka wymagającego zarządzania pamięcią (takiego jak C lub C ++). Najlepszym sposobem zilustrowania tego IMHO jest diagram:
Zauważ, że jest to bardzo uproszczony widok specyficzny dla systemu Windows.
Sposobem na zrozumienie tego diagramu jest to, że im wyżej na diagramie znajduje się metoda alokacji pamięci, tym wyższy poziom używa jej implementacji. Ale zacznijmy od dołu.
Zapewnia wszystkie rezerwacje i alokacji pamięci dla systemu operacyjnego, jak również wsparcie dla plików pamięci odwzorowany , współdzielonej pamięci , kopiowanie przy zapisie operacji, itd. To nie jest dostępny bezpośrednio z kodu trybu użytkownika, więc pominę to tutaj.
Są to interfejsy API najniższego poziomu dostępne w trybie użytkownika . VirtualAlloc
Funkcja zasadzie wywołuje ZwAllocateVirtualMemory który z kolei dokonuje szybkiej syscall się ring0
do dalszego przetwarzania spycha do menedżera pamięci jądra. Jest to również najszybsza metoda rezerwacji / alokacji bloku nowej pamięci spośród wszystkich dostępnych w trybie użytkownika.
Ale ma dwa główne warunki:
Przydziela tylko bloki pamięci wyrównane na granicy szczegółowości systemu.
Przydziela tylko bloki pamięci o rozmiarze będącym wielokrotnością ziarnistości systemu.
Jaka jest więc szczegółowość systemu ? Możesz to uzyskać, wywołując GetSystemInfo . Jest zwracany jako dwAllocationGranularity
parametr. Jego wartość jest specyficzna dla implementacji (i prawdopodobnie sprzętu), ale w wielu 64-bitowych systemach Windows jest ustawiana na 0x10000
bajty lub 64K
.
To wszystko oznacza, że jeśli spróbujesz przydzielić, powiedz tylko 8-bajtowy blok pamięci z VirtualAlloc
:
void* pAddress = VirtualAlloc(NULL, 8, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
Jeśli się powiedzie, pAddress
zostanie wyrównany na 0x10000
granicy bajtów. I nawet jeśli zażądałeś tylko 8 bajtów, rzeczywisty blok pamięci, który otrzymasz, będzie całym page
(lub czymś w rodzaju 4K
bajtów. Dokładny rozmiar strony jest zwracany w dwPageSize
parametrze). Ale poza tym cały blok pamięci zakres 0x10000
bajtów (lub 64K
w większości przypadków) z pAddress
nie będzie dostępny dla dalszych alokacji. W pewnym sensie, przydzielając 8 bajtów, równie dobrze możesz poprosić o 65536.
Zatem morał z tej historii nie polega na zastępowaniu VirtualAlloc
ogólnych alokacji pamięci w aplikacji. Musi być używany w bardzo szczególnych przypadkach, tak jak jest to zrobione w przypadku stosu poniżej. (Zwykle do rezerwowania / przydzielania dużych bloków pamięci).
VirtualAlloc
Nieprawidłowe użycie może prowadzić do poważnej fragmentacji pamięci.
W skrócie, funkcje sterty są w zasadzie opakowaniem dla VirtualAlloc
funkcji. Inne odpowiedzi tutaj stanowią całkiem niezłą koncepcję tego. Dodam, że w bardzo uproszczonym ujęciu sposób działania sterty jest następujący:
HeapCreate
rezerwuje duży blok pamięci wirtualnej przez wywołanie VirtualAlloc
wewnętrzne (a ZwAllocateVirtualMemory
konkretnie). Tworzy również wewnętrzną strukturę danych, która może śledzić dalsze alokacje mniejszych rozmiarów w zarezerwowanym bloku pamięci wirtualnej.
Wszelkie wywołania HeapAlloc
i HeapFree
faktycznie nie przydzielają / zwalniają żadnej nowej pamięci (chyba że, oczywiście, żądanie przekracza to, co zostało już zarezerwowane HeapCreate
), ale zamiast tego odliczają (lub commit
) wcześniej zarezerwowany duży fragment, dzieląc go na mniejsze bloki pamięci, które żąda użytkownik.
HeapDestroy
z kolei wywołania, VirtualFree
które faktycznie zwalniają pamięć wirtualną.
Wszystko to sprawia, że funkcje sterty są idealnymi kandydatami do ogólnego przydziału pamięci w aplikacji. Świetnie nadaje się do alokacji pamięci o dowolnym rozmiarze. Jednak niewielką ceną za wygodę funkcji sterty jest to, że wprowadzają one niewielki narzut VirtualAlloc
przy rezerwowaniu większych bloków pamięci.
Kolejną dobrą rzeczą w sterty jest to, że tak naprawdę nie musisz jej tworzyć. Zwykle jest tworzony dla Ciebie, gdy rozpoczyna się Twój proces. Można więc uzyskać do niego dostęp, wywołując funkcję GetProcessHeap .
Jest opakowaniem specyficznym dla języka dla funkcji sterty . W przeciwieństwie HeapAlloc
, HeapFree
itd te funkcje nie będą działać tylko wtedy, gdy kod jest kompilowany dla systemu Windows, ale również dla innych systemów operacyjnych (takich jak Linux, itd)
Jest to zalecany sposób przydzielania / zwalniania pamięci, jeśli programujesz w języku C. (Chyba że kodujesz określony sterownik urządzenia trybu jądra).
Przyjdź jako operatorzy zarządzania pamięcią wysokiego poziomu (no cóż, dla C++
). Są specyficzne dla C++
języka i podobnie jak malloc
dla C
, są również opakowaniami dla heap
funkcji. Mają też całą masę własnego kodu, który zajmuje się C++
specyficzną inicjalizacją konstruktorów, zwalnianiem alokacji w destruktorach, zgłaszaniem wyjątków itp.
Te funkcje są zalecanym sposobem przydzielania / zwalniania pamięci i obiektów, jeśli programujesz w C++
.
Na koniec chciałbym poruszyć jeden komentarz dotyczący tego, co zostało powiedziane w innych odpowiedziach na temat VirtualAlloc
współdzielenia pamięci między procesami. VirtualAlloc
samo w sobie nie pozwala na współdzielenie zarezerwowanej / alokowanej pamięci z innymi procesami. W tym celu należy użyć CreateFileMapping
interfejsu API, który może utworzyć nazwany blok pamięci wirtualnej, który można udostępniać innym procesom. Może również mapować plik na dysku do pamięci wirtualnej w celu uzyskania dostępu do odczytu / zapisu. Ale to inny temat.
VirtualAlloc
jest wyspecjalizowaną alokacją systemu pamięci wirtualnej (VM) systemu operacyjnego. Alokacje w systemie maszyny wirtualnej muszą być dokonywane na poziomie szczegółowości alokacji, która (szczegółowość alokacji) zależy od architektury. Alokacja w systemie VM jest jedną z najbardziej podstawowych form alokacji pamięci. Alokacje maszyn wirtualnych mogą przybierać różne formy, pamięć niekoniecznie musi być dedykowana lub fizycznie zabezpieczona w pamięci RAM (choć może tak być). Alokacja maszyn wirtualnych jest zwykle alokacją specjalnego przeznaczenia , ponieważ alokacja musi
HeapAlloc
jest w zasadzie to, co malloc
i new
jak w końcu zadzwonić. Został zaprojektowany tak, aby był bardzo szybki i użyteczny w wielu różnych typach scenariuszy alokacji ogólnego przeznaczenia. Jest to „Sterta” w klasycznym tego słowa znaczeniu. Sterty są w rzeczywistości konfigurowane przez a VirtualAlloc
, który jest używany do początkowej rezerwacji miejsca alokacji z systemu operacyjnego. Po zainicjowaniu przestrzeni przez VirtualAlloc
, różne tabele, listy i inne struktury danych są konfigurowane w celu utrzymania i sterowania działaniem HEAP. Część tej operacji polega na dynamicznym skalowaniu (powiększaniu i zmniejszaniu) pryzmy, dostosowywaniu pryzmy do określonych zastosowań (częste alokacje pewnego rozmiaru) itp.
new
i malloc
są nieco takie same, malloc
jest zasadniczo dokładnym wezwaniem do HeapAlloc( heap-id-default )
; new
jednakże może [dodatkowo] skonfigurować przydzieloną pamięć dla obiektów C ++ . Dla danego obiektu C ++ będzie przechowywać vtables na stercie dla każdego wywołującego. Te tabele vtables są przekierowaniami do wykonania i stanowią część tego, co nadaje C ++ jego cechy OO, takie jak dziedziczenie, przeciążanie funkcji itp.
Niektóre inne popularne metody alokacji, takie jak _alloca()
i _malloca()
są oparte na stosie ; FileMappings są tak naprawdę przydzielane VirtualAlloc
i ustawiane za pomocą określonych flag bitowych, które określają te mapowania jako typu FILE
.
W większości przypadków pamięć należy przydzielać w sposób zgodny z wykorzystaniem tej pamięci;). new
w C ++, malloc
dla C, VirtualAlloc
dla przypadków masowych lub IPC.
*** Uwaga, duże alokacje pamięci wykonywane przez HeapAlloc
są w rzeczywistości wysyłane VirtualAlloc
po pewnym rozmiarze (kilkaset k lub 16 MB lub coś, o czym zapomniałem, ale dość duże :)).
*** EDYCJA Krótko wspomniałem o IPC i VirtualAlloc
jest też coś bardzo fajnego w powiązaniu, o VirtualAlloc
którym żaden z respondentów nie omówił.
VirtualAlloc
Ex jest tym, czego jeden proces może użyć do przydzielenia pamięci w przestrzeni adresowej innego procesu. Najczęściej jest to używane w połączeniu, aby uzyskać zdalne wykonanie w kontekście innego procesu za pośrednictwem CreateRemoteThread (podobnie jak CreateThread
wątek jest po prostu uruchamiany w innym procesie).
W konturze:
VirtualAlloc, HeapAlloc itp. To interfejsy API systemu Windows, które przydzielają pamięć różnych typów bezpośrednio z systemu operacyjnego. VirtualAlloc zarządza stronami w systemie pamięci wirtualnej Windows, podczas gdy HeapAlloc przydziela z określonej sterty systemu operacyjnego. Szczerze mówiąc, prawdopodobnie nigdy nie będziesz musiał używać żadnego z nich.
malloc to standardowa funkcja biblioteki C (i C ++), która przydziela pamięć do twojego procesu. Implementacje malloc zazwyczaj używają jednego z interfejsów API systemu operacyjnego do tworzenia puli pamięci podczas uruchamiania aplikacji, a następnie alokowania z niej podczas wykonywania żądań malloc
new to standardowy operator C ++, który przydziela pamięć, a następnie odpowiednio wywołuje konstruktory w tej pamięci. Może być zaimplementowany w zakresie malloc lub w zakresie interfejsów API systemu operacyjnego, w którym to przypadku również zwykle tworzy pulę pamięci podczas uruchamiania aplikacji.
VirtualAlloc
===> sbrk()
pod UNIXem
HeapAlloc
====> malloc()
w systemie UNIX
VirtualAlloc
=> Alokuje bezpośrednio do pamięci wirtualnej, rezerwujesz / zatwierdzasz w blokach. Jest to świetne rozwiązanie w przypadku dużych alokacji, na przykład dużych tablic.
HeapAlloc
/ new
=> alokuje pamięć na domyślnym stosie (lub innym stosie, który możesz utworzyć). To przydziela na obiekt i jest świetne dla mniejszych obiektów. Domyślna sterta jest możliwa do serializacji, dlatego ma gwarantowaną alokację wątków (może to powodować pewne problemy w scenariuszach o wysokiej wydajności i dlatego można tworzyć własne sterty).
malloc
=> używa sterty środowiska wykonawczego C, podobnie jak w HeapAlloc
przypadku scenariuszy zgodności, ale jest to powszechne.
W skrócie, sterta to po prostu fragment pamięci wirtualnej zarządzanej przez menedżera sterty (zamiast surowej pamięci wirtualnej)
Ostatnim modelem w świecie pamięci są pliki mapowane w pamięci, ten scenariusz jest świetny dla dużych porcji danych (takich jak duże pliki). Jest to używane wewnętrznie podczas otwierania pliku EXE (nie ładuje pliku EXE do pamięci, tylko tworzy plik mapowany w pamięci).