Є багато методи виділення пам'яті в середовищі Windows, такі як VirtualAlloc
, HeapAlloc
, malloc
, new
.
Отже, яка різниця між ними?
Відповіді:
Кожен API призначений для різного використання. Кожен з них також вимагає використання правильної функції вивільнення / звільнення, коли закінчите з пам'яттю.
Низькорівневий API Windows, який надає безліч опцій, але в основному корисний для людей у досить конкретних ситуаціях. Може виділяти пам’ять лише у більших шматках (редагувати: не 4 КБ). Бувають ситуації, коли це вам потрібно, але ви будете знати, коли потрапите в одну з цих ситуацій. Одне з найпоширеніших - якщо вам доводиться ділитися пам’яттю безпосередньо з іншим процесом. Не використовуйте його для розподілу пам'яті загального призначення. Використовуйте VirtualFree
для вивільнення.
Виділяє будь-який обсяг пам’яті, який ви просите, не великими шматками, ніж VirtualAlloc
. HeapAlloc
знає, коли йому потрібно зателефонувати, VirtualAlloc
і робить це для вас автоматично. Подобається malloc
, але лише для Windows і надає ще кілька варіантів. Підходить для виділення загальних фрагментів пам'яті. Деякі API Windows можуть вимагати, щоб ви використовували це для виділення пам'яті, яку ви їм передаєте, або використовувати його супутник, HeapFree
щоб звільнити пам’ять, яку вони вам повертають.
C спосіб розподілу пам'яті. Віддайте перевагу цьому, якщо ви пишете на C, а не на C ++, і хочете, щоб ваш код працював, наприклад, на комп'ютерах Unix, або хтось конкретно каже, що вам потрібно його використовувати. Не ініціалізує пам’ять. Підходить для виділення загальних фрагментів пам'яті, наприклад HeapAlloc
. Простий API. Використовуйте free
для вивільнення. malloc
Виклики Visual C ++ HeapAlloc
.
Спосіб розподілу пам'яті на C ++. Віддайте перевагу цьому, якщо ви пишете на C ++. Він також поміщає об'єкт або об'єкти у виділену пам'ять. Використовуйте delete
для вивільнення (або delete[]
для масивів). new
Виклики Visual Studio HeapAlloc
, а потім, можливо, ініціалізують об’єкти, залежно від того, як ви це називаєте.
В останні стандарти C ++ (C ++ 11 і вище), якщо ви повинні вручну використання delete
, ви робите це неправильно , і повинні використовувати смарт - покажчик , як unique_ptr
замість цього. Починаючи з C ++ 14 і далі, те саме можна сказати про це new
(замінено такими функціями, як make_unique()
).
Є також ще кілька подібних функцій, таких як SysAllocString
вам можуть сказати, що вам доведеться використовувати за певних обставин.
Дуже важливо зрозуміти різницю між API розподілу пам’яті (в Windows), якщо ви плануєте використовувати мову, яка вимагає управління пам’яттю (наприклад, C або C ++.) І найкращий спосіб проілюструвати це IMHO - це схема:
Зверніть увагу, що це дуже спрощений подання для Windows.
Спосіб зрозуміти цю діаграму полягає в тому, що чим вище на схемі метод розподілу пам’яті, тим вищий рівень реалізації він використовує. Але почнемо знизу.
Він забезпечує всі резервування пам’яті та розподіл для операційної системи, а також підтримку файлів , що відображаються в пам’яті , спільної пам’яті , операцій копіювання та запису тощо. До неї безпосередньо не можна отримати доступ із коду користувацького режиму, тому я пропущу це тут.
Це API нижчого рівня, доступні в режимі користувача . VirtualAlloc
Функція в основному викликає ZwAllocateVirtualMemory , що в свою чергу робить швидкий системний виклик , щоб ring0
звести подальшу обробку менеджеру пам'яті ядра. Це також найшвидший спосіб зарезервувати / розподілити блок нової пам'яті з усієї доступної в користувацькому режимі.
Але це має дві основні умови:
Він виділяє лише блоки пам'яті, вирівняні на межі системної деталізації.
Він виділяє лише блоки пам'яті розміром, кратним системній деталізації.
То що це за деталізація системи ? Ви можете отримати його, зателефонувавши GetSystemInfo . Він повертається як dwAllocationGranularity
параметр. Його значення залежить від реалізації (і, можливо, апаратного забезпечення), але в багатьох 64-розрядних системах Windows воно встановлюється в 0x10000
байтах, або 64K
.
Отже, що все це означає, це те, що якщо ви намагаєтеся розподілити, скажіть лише 8-байтовий блок пам'яті з VirtualAlloc
:
void* pAddress = VirtualAlloc(NULL, 8, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
У разі успіху pAddress
буде вирівняно на межі 0x10000
байта. І навіть незважаючи на те, що ви запитали лише 8 байт, фактичним блоком пам'яті, який ви отримаєте, буде весь page
(або щось на зразок 4K
байтів. Точний розмір сторінки повертається в dwPageSize
параметрі.) Але, крім цього, весь блок пам'яті затягують 0x10000
байти (або 64K
в більшості випадків) від pAddress
волі не будуть доступні для будь - яких додаткових асигнувань. Отже, у певному сенсі, виділивши 8 байт, ви могли б також вимагати 65536.
Тож мораль історії тут не полягає в тому, щоб замінити VirtualAlloc
загальні розподіли пам’яті у вашому додатку. Його потрібно використовувати для дуже конкретних випадків, як це робиться з купою нижче. (Зазвичай для резервування / розподілу великих блоків пам'яті.)
VirtualAlloc
Неправильне використання може призвести до сильної фрагментації пам'яті.
У двох словах, функції купи в основному є обгорткою для VirtualAlloc
функції. Інші відповіді тут дають досить хорошу концепцію цього. Додам, що, в дуже спрощеному поданні, купа працює так:
HeapCreate
резервує великий блок віртуальної пам'яті шляхом VirtualAlloc
внутрішнього виклику (або, ZwAllocateVirtualMemory
якщо бути конкретним). Він також встановлює внутрішню структуру даних, яка може відслідковувати подальші розподіли менших розмірів у зарезервованому блоці віртуальної пам'яті.
Будь-які дзвінки HeapAlloc
та HeapFree
фактично не виділяють / звільняють нову пам’ять (якщо, звичайно, запит не перевищує того, що вже зарезервовано HeapCreate
), але замість цього вони вимірюють (або commit
) раніше зарезервований великий шматок, розтинаючи його на менші блоки пам'яті, які запити користувача.
HeapDestroy
у свою чергу дзвінки, VirtualFree
які фактично звільняють віртуальну пам’ять.
Отже, все це робить функції купи ідеальними кандидатами для загального розподілу пам’яті у вашому додатку. Він чудово підходить для розміщення пам'яті довільного розміру. Але невелика ціна, яку потрібно заплатити за зручність функцій купи, полягає в тому, що вони створюють невеликі накладні витрати VirtualAlloc
при резервуванні більших блоків пам'яті.
Ще одна хороша річ купи - те, що вам насправді не потрібно її створювати. Зазвичай він створюється для вас, коли починається процес. Тож можна отримати до нього доступ, зателефонувавши до функції GetProcessHeap .
Це мовна оболонка для функцій купи . В відміну HeapAlloc
, HeapFree
і т.д. ці функції будуть працювати не тільки тоді , коли ваш код скомпільовано для Windows, але і для інших операційних систем (наприклад, Linux і т.д.)
Це рекомендований спосіб розподілу / звільнення пам'яті, якщо ви програмуєте на C. (Якщо ви не кодуєте певний драйвер пристрою в режимі ядра).
Станьте оператором управління пам’яттю високого рівня (ну, для C++
). Вони специфічні для C++
мови, і, як і malloc
для C
, також є обгортками для heap
функцій. Вони також мають цілу купу власного коду, який займається - C++
конкретною ініціалізацією конструкторів, вивільненням деструкторів, винятком тощо.
Ці функції є рекомендованим способом розподілу / звільнення пам'яті та об'єктів, якщо ви програмуєте C++
.
Нарешті, я хочу зробити один коментар щодо того, що було сказано в інших відповідях щодо використання VirtualAlloc
для спільного використання пам'яті між процесами. VirtualAlloc
сам по собі не дозволяє спільне використання своєї зарезервованої / виділеної пам'яті з іншими процесами. Для цього потрібно використовувати CreateFileMapping
API, який може створити іменований блок віртуальної пам'яті, який можна спільно використовувати з іншими процесами. Він також може перетворити файл на диск на віртуальну пам'ять для доступу до читання / запису. Але це вже інша тема.
VirtualAlloc
- це спеціалізоване виділення системи віртуальної пам’яті ОС (VM). Розподіл у системі ВМ повинен здійснюватися при деталізації розподілу, яка (деталізація розподілу) залежить від архітектури. Розподіл у системі ВМ є однією з найосновніших форм розподілу пам'яті. Розподіл віртуальних машин може мати кілька форм, пам’ять не обов’язково виділяється або фізично підтримується в оперативній пам'яті (хоча це може бути). Виділення VM, як правило, є виділенням спеціального призначення , або через розподіл, який повинен
HeapAlloc
по суті , що malloc
і new
як в кінцевому підсумку викликати. Він розроблений для того, щоб бути дуже швидким та придатним для використання у багатьох різних типах сценаріїв загального призначення. Це "Купи" в класичному розумінні. Купи фактично налаштовуються за допомогою a VirtualAlloc
, який використовується для початкового резервування місця виділення з ОС. Після ініціалізації простору VirtualAlloc
різні таблиці, списки та інші структури даних налаштовуються на підтримку та контроль роботи HEAP. Частина цієї операції полягає у формі динамічного розміру (вирощування та зменшення) купи, пристосування купи до певного звичаю (часті виділення певного розміру) тощо.
new
і malloc
дещо однакові, malloc
це, по суті, точний заклик до HeapAlloc( heap-id-default )
; new
однак може [додатково] налаштувати виділену пам'ять для об'єктів C ++ . Для даного об'єкта C ++ зберігатиме таблиці vtables у купі для кожного абонента. Ці vtables є переспрямуваннями на виконання та є частиною того, що надає C ++ його OO-характеристики, такі як успадкування, перевантаження функцій тощо ...
Деякі інші методи загального розподілу , як _alloca()
і _malloca()
в стек основі; FileMappings дійсно виділяються VirtualAlloc
та встановлюються з певними бітовими прапорами, які позначають ці зіставлення як тип FILE
.
Більшу частину часу слід розподіляти пам’ять так, щоб це відповідало використанню цієї пам’яті;). new
в C ++, malloc
для C, VirtualAlloc
для масових випадків або випадків IPC.
*** Зверніть увагу, що великі розподіли пам’яті, здійснені HeapAlloc
, фактично відправляються VirtualAlloc
після певного розміру (пару сотень к або 16 МБ, або щось я забув, але досить велике :)).
*** EDIT Я коротко зауважив про МПК, і VirtualAlloc
є також щось дуже акуратне у пов'язаному, VirtualAlloc
що ніхто з респондентів на це питання не обговорював.
VirtualAlloc
Ex - це те, що один процес може використовувати для розподілу пам'яті в адресному просторі іншого процесу. Найчастіше це використовується в поєднанні для отримання віддаленого виконання в контексті іншого процесу за допомогою CreateRemoteThread (подібно до того CreateThread
, що потік просто запускається в іншому процесі).
У контурі:
VirtualAlloc, HeapAlloc тощо - це API Windows, які безпосередньо виділяють пам'ять різних типів з ОС. VirtualAlloc керує сторінками у системі віртуальної пам'яті Windows, тоді як HeapAlloc виділяє з певної купи ОС. Чесно кажучи, навряд чи вам коли-небудь потрібно буде використовувати будь-який із них.
malloc - це стандартна функція бібліотеки C (і C ++), яка виділяє пам'ять для вашого процесу. Реалізації malloc, як правило, використовують один з API ОС для створення пулу пам’яті під час запуску програми, а потім виділяють з неї під час надсилання запитів malloc
new - це стандартний оператор C ++, який виділяє пам'ять, а потім викликає конструктори відповідно до цієї пам'яті. Він може бути реалізований з точки зору malloc або з точки зору API API, і в цьому випадку він також зазвичай створює пул пам'яті під час запуску програми.
VirtualAlloc
===> sbrk()
під UNIX
HeapAlloc
====> malloc()
під UNIX
VirtualAlloc
=> Виділяє прямо у віртуальну пам’ять, ви резервуєте / фіксуєте блоками. Це чудово для великих розподілів, наприклад великих масивів.
HeapAlloc
/ new
=> виділяє пам'ять у купі за замовчуванням (або будь-якій іншій купі, яку ви можете створити). Це виділяє на об’єкт і чудово підходить для менших об’єктів. Купи за замовчуванням можна серіалізувати, тому він має гарантований розподіл потоків (це може спричинити деякі проблеми у сценаріях високої продуктивності, і тому ви можете створити власні купи).
malloc
=> використовує купу середовища виконання C, подібну до, HeapAlloc
але вона є загальною для сценаріїв сумісності.
У двох словах, купа - це лише шматок віртуальної пам'яті, якою керує диспетчер купи (а не необроблена віртуальна пам'ять)
Остання модель у світі пам'яті - це файли, зіставлені з пам'яттю, цей сценарій чудово підходить для великої кількості даних (наприклад, великих файлів). Це використовується внутрішньо, коли ви відкриваєте EXE (він не завантажує EXE в пам'ять, а лише створює файл, зіставлений із пам'яттю).