Чому malloc ініціалізує значення до 0 у gcc?


78

Можливо, це різниться від платформи до платформи, але

коли я компілюю за допомогою gcc і запускаю наведений нижче код, я щоразу отримую 0 в моєму ubuntu 11.10.

Чому malloc поводиться так, хоча є мозоль?

Чи не означає це, що є небажані накладні витрати, щоб просто ініціалізувати значення до 0, навіть якщо ви іноді не хочете, щоб це було?


EDIT: О, мій попередній приклад не ініціалізувався, але випадково використовував "свіжий" блок.

Я саме шукав, чому він ініціалізує його, коли виділяє великий блок:

Але дякую за те, що ви вказали, що є причина БЕЗПЕКИ під час прогрівання! (Ніколи про це не думав). Звичайно, він повинен ініціалізуватись до нуля при виділенні свіжого блоку або великого блоку.


13
Для більш реалістичного тесту ви пробували розподіляти, звільняти, а потім розподіляти знову (можливо, повторювати кожен кілька разів)? Те, що malloc повертає пам'ять, ініціалізовану нулем, вперше не означає, що ви можете розраховувати на неї загалом.
user786653

1
Також може бути так, що операційна система чи щось подібне встановила для пам’яті значення 0 і mallocне мала до цього ніякого відношення.
Сет Карнегі,

Відповіді:


179

Коротка відповідь:

Це не так, у вашому випадку це просто дорівнює нулю.
(Також ваш тест не показує, що дані дорівнюють нулю. Він показує лише, якщо один елемент дорівнює нулю.)


Довга відповідь:

Коли ви телефонуєте malloc(), відбудеться одна з двох речей:

  1. Він переробляє пам’ять, яка була раніше виділена і звільнена від того самого процесу.
  2. Він вимагає нових сторінок від операційної системи.

У першому випадку пам’ять буде містити дані, що залишилися з попередніх розподілів. Тож не буде нулем. Це звичайний випадок при виконанні невеликих розподілів.

У другому випадку пам’ять буде від ОС. Це трапляється, коли у програми закінчується пам’ять - або коли ви вимагаєте дуже великого розподілу. (як у вашому прикладі)

Ось кришка: пам’ять, що надходить від ОС, буде обнулена з міркувань безпеки . *

Коли ОС надає вам пам’ять, вона могла бути звільнена від іншого процесу. Щоб пам’ять могла містити конфіденційну інформацію, таку як пароль. Отже, щоб ви не читали такі дані, ОС обнулить їх, перш ніж вони вам їх передадуть.

* Зауважу, що стандарт C про це нічого не говорить. Це суворо поведінка ОС. Отже, це занулення може бути присутнім, а може і не бути в системах, де безпека не є проблемою.


Щоб надати цьому більше результатів:

Як @R. згадується в коментарях, це обнулення є причиною, чому ви завжди повинні використовувати calloc()замість malloc()+memset() . calloc()може скористатися цим фактом, щоб уникнути окремого memset().


З іншого боку, це занулення іноді є вузьким місцем для продуктивності. У деяких числових програмах (наприклад, невідповідний ШПФ ) вам потрібно виділити величезну кількість подряпин пам'яті. Використовуйте його для виконання будь-якого алгоритму, а потім звільніть його.

У цих випадках занулення є непотрібним і дорівнює чистим накладним витратам.

Найекстремальніший приклад, який я бачив, - це 20-секундна нульова витрата на 70-секундну операцію з буфером подряпин 48 ГБ. (Приблизно 30% накладних витрат.) (Звичайно: у машини бракувало пропускної здатності пам'яті.)

Очевидним рішенням є просто повторне використання пам'яті вручну. Але це часто вимагає прориву усталених інтерфейсів. (особливо якщо це частина бібліотечної процедури)


14
Але ви все ще не можете розраховувати на те, що він дорівнює нулю, якщо ви цього не зробите самі (або за допомогою calloc, який робить це за вас після отримання пам'яті з ОС).
Greg Hewgill

Дякую за вашу відповідь. Ніколи не думав, що під час mallocing виникне проблема безпеки!
SHH

34
Це тонко. Коли ОС надає вам пам’ять, вона могла бути звільнена від іншого процесу. Щоб пам’ять могла містити конфіденційну інформацію, таку як пароль. Отже, щоб ви не читали такі дані, ОС обнулить їх, перш ніж вони вам їх передадуть. Але це деталь реалізації та може бути іншою, як у деяких вбудованих системах.
Містичний

21
Це трохи осторонь питання ОП, але одним із наслідків цього ефекту є те, що ви завжди повинні використовувати callocзамість malloc+, memsetколи вам потрібна нульова ініціалізована пам'ять (принаймні для великих блоків, де час до нуля може мати значення). malloc+ memsetзавжди буде вимагати великих витрат на запис у весь блок, але система callocможе скористатися тим, що нова анонімна пам’ять спочатку буде заповнена нулем.
R .. GitHub СТОП ДОПОМОГАЙ ЛЕД

4
Відповіді на це запитання можуть допомогти вам це зрозуміти. Ядро може обдурити calloc, фактично не виписуючи всі обнулені сторінки, поки вони не будуть використані. Мемсет (очевидно) змушує сторінки негайно виписувати. Більше інформації за посиланням.
thomasrutter

21

Зазвичай ОС очищає нові сторінки пам’яті, які вона надсилає у ваш процес, тому вона не може переглядати дані попереднього процесу. Це означає, що під час першої ініціалізації змінної (або чогось, що робить malloc) вона часто буде нульовою, але якщо ви коли-небудь повторно використаєте цю пам’ять (наприклад, звільнивши її та зробивши ще одну помилку), то всі ставки вимкнені.

Ця невідповідність саме тому, чому неініціалізовані змінні так важко знайти помилку.


Що стосується небажаних накладних витрат, уникати неуточненої поведінки, мабуть, важливіше . Який би незначний приріст продуктивності ви не могли отримати в цьому випадку, не компенсує важко знайти помилки, з якими вам доведеться мати справу, якщо хтось трохи змінить коди (порушуючи попередні припущення) або перенесе їх на іншу систему (де ці припущення могли бути невірними на першому місці).


4
+1 ... не впевнений, чи потрібно "можливо" в

18

Чому ви вважаєте, що malloc()ініціалізується до нуля? Буває так, що перший виклик malloc()результату - дзвінок sbrkабоmmapсистемні виклики, які виділяють сторінку пам’яті з ОС. ОС зобов'язана надати нульову ініціалізовану пам'ять з міркувань безпеки (інакше дані інших процесів стають видимими!). Тож можна подумати - ОС марно витрачає час на обнулення сторінки. Але не! У Linux існує спеціальна загальносистемна одностороння сторінка, яка називається `` нульова сторінка '', і ця сторінка буде відображена як Copy-On-Write, що означає, що лише коли ви фактично пишете на цій сторінці, ОС виділить іншу сторінку і ініціалізувати його. Тож сподіваюся, це відповідає на ваше запитання щодо ефективності. Модель підкачки пам'яті дозволяє використовувати пам'ять якось ліниво, підтримуючи можливість багаторазового відображення однієї і тієї ж сторінки плюс можливість обробляти справи, коли відбувається перша запис.

Якщо ви зателефонуєте free(), glibcрозподільник поверне регіон до своїх безкоштовних списків, і при malloc()повторному виклику ви можете отримати той самий регіон, але забруднений попередніми даними. Зрештою, free()може повернути пам’ять до ОС, повторно викликавши системні дзвінки.

Зверніть увагу , що glibc сторінка людей на malloc()строго говорить , що не очищує, тому в «договорі» на API, ви не можете припустити , що він очищений. Ось оригінальний уривок:

malloc () виділяє байти розміру і повертає покажчик на виділену пам'ять.
Пам'ять не очищена. Якщо розмір дорівнює 0, тоді malloc () повертає або NULL, або унікальне значення покажчика, яке згодом може бути успішно передано у free ().

Якщо ви хочете, ви можете прочитати більше про цю документацію, якщо вас турбує продуктивність або інші побічні ефекти.


15

Я змінив ваш приклад, щоб містити 2 однакові розподіли. Тепер легко зрозуміти, mallocчи не нульова ініціалізація пам’яті.

Вихід з gcc 4.3.4


Я спробував те, що ви зробили, і якщо тоді я виділю лише 100 байтів, навіть незважаючи на те, що вказівник вказує на ту саму адресу, значення за цією адресою відрізняється. Якщо я виділю 400 або більше байт, тоді і значення покажчика, і значення в пам'яті однакові. Що, на вашу думку, може бути причиною?
yoyo_fun

3

Від gnu.org :

Дуже великі блоки (набагато більші за сторінку) виділяються mmap (анонімним або через / dev / zero ) за допомогою цієї реалізації.


OP, однак, робить mallocing невеликими кроками. Чи є в цьому посиланні щось про це теж?
hugomg

2

Стандарт не диктує, що malloc()має ініціалізувати значення до нуля. Просто на вашій платформі трапляється, що воно може бути встановлене на нуль, або, можливо, воно було нулем у той момент, коли ви прочитали це значення.


2

Ваш код не демонструє, що mallocініціалізує пам’ять до 0. Це може зробити операційна система до запуску програми. Щоб побачити, що це так, запишіть в пам’ять інше значення, звільніть його і знову зателефонуйте malloc. Ймовірно, ви отримаєте ту саму адресу, але вам доведеться це перевірити. Якщо так, ви можете подивитися, що він містить. Повідомте нас!


0

Чи знаєте ви, що він безумовно ініціюється? Чи можливо, що область, повернута malloc (), часто на початку має 0?


0

Ніколи не розраховуйте на те, що будь-який компілятор генерує код, який ініціалізує пам'ять до чого-небудь. malloc просто повертає покажчик на n байт пам’яті, десь пекло може бути навіть у swap.

Якщо вміст пам'яті критично важливий, ініціалізуйте його самостійно.


4
За винятком випадків, коли мова гарантує її ініціалізацію. Статичні об'єкти без явної ініціалізації неявно ініціалізуються до нуля.
Кіт Томпсон,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.