Защо malloc инициализира стойностите на 0 в gcc?

Може би е различно от платформа на платформа, но

когато компилирам с gcc и стартирам кода по-долу, получавам 0 всеки път в моя ubuntu 11.10.

#include <stdio.h>
#include <stdlib.h>

int main()
{
    double *a = (double*) malloc(sizeof(double)*100)
    printf("%f", *a);
}

Защо malloc се държи така, въпреки че има calloc?

Не означава ли, че има нежелано натоварване на производителността само за инициализиране на стойностите на 0, дори ако понякога не искате да е така?


РЕДАКТИРАНЕ: О, предишният ми пример не беше initiazling, но се случи да използвам "свеж" блок.

Това, което точно търсех, беше защо го инициализира, когато разпределя голям блок:

int main()
{
    int *a = (int*) malloc(sizeof(int)*200000);
    a[10] = 3;
    printf("%d", *(a+10));

    free(a);

    a = (double*) malloc(sizeof(double)*200000);
    printf("%d", *(a+10));
}

OUTPUT: 3
        0 (initialized)

Но благодаря, че посочихте, че има причина за СИГУРНОСТ при mallocing! (Никога не съм мислил за това). Разбира се, че трябва да се инициализира до нула, когато разпределя нов блок или големия блок.


person SHH    schedule 06.11.2011    source източник
comment
За по-реалистичен тест, опитвали ли сте да разпределите, освободите и след това да разпределите отново (евентуално повтаряйки всяко няколко пъти)? Само защото malloc връща нулева инициализирана памет за първи път, не означава, че можете да разчитате на него като цяло.   -  person user786653    schedule 06.11.2011
comment
Възможно е също паметта да е зададена на 0 от операционната система или нещо подобно и malloc да няма нищо общо с това.   -  person Seth Carnegie    schedule 06.11.2011
comment
Не прехвърляйте резултата от malloc в C   -  person phuclv    schedule 16.10.2017


Отговори (10)


Кратък отговор:

Не е така, просто случайно е нула във вашия случай.
(Също така вашият тестов случай не показва, че данните са нула. Показва само ако един елемент е нула.)


Дълъг отговор:

Когато се обадите на malloc(), ще се случи едно от двете неща:

  1. Той рециклира памет, която преди това е била разпределена и освободена от същия процес.
  2. Той изисква нова(и) страница(и) от операционната система.

В първия случай паметта ще съдържа данни, останали от предишни разпределения. Така че няма да е нула. Това е обичайният случай при извършване на малки разпределения.

Във втория случай паметта ще е от ОС. Това се случва, когато програмата изчерпи паметта - или когато поискате много голямо разпределение. (какъвто е случаят във вашия пример)

Ето уловката: Паметта, идваща от операционната система, ще бъде нулирана от съображения за сигурност.*

Когато ОС ви дава памет, тя може да е била освободена от друг процес. Така че тази памет може да съдържа чувствителна информация като парола. Така че, за да ви попречи да четете такива данни, операционната система ще ги нулира, преди да ви ги даде.

*Отбелязвам, че C стандартът не казва нищо за това. Това е строго поведение на ОС. Така че това нулиране може или не може да присъства на системи, където сигурността не е проблем.


За да дадете повече представа за ефективността на това:

Като @R. споменава в коментарите, това нулиране е причината, поради която винаги трябва да използвате calloc() вместо malloc() + memset(). calloc() може да се възползва от този факт, за да избегне отделен memset().


От друга страна, това нулиране понякога е тясно място на производителността. В някои числени приложения (като извън място FFT), трябва да разпределите огромна част от скреч паметта. Използвайте го, за да изпълните произволен алгоритъм, след което го освободете.

В тези случаи нулирането е ненужно и се равнява на чисти разходи.

Най-екстремният пример, който съм виждал, е 20-секундно нулиране за 70-секундна операция с 48 GB скреч буфер. (Приблизително 30% режийни разходи.) (Разбира се: машината наистина нямаше честотна лента на паметта.)

Очевидното решение е просто да използвате отново паметта ръчно. Но това често изисква пробиване през установените интерфейси. (особено ако е част от рутина в библиотеката)

person Mysticial    schedule 06.11.2011
comment
Но вие все пак не можете да разчитате, че е нула, освен ако не го направите сами (или с calloc, който го прави вместо вас, след като получите памет от операционната система). - person Greg Hewgill; 06.11.2011
comment
Благодаря за вашият отговор. Никога не съм мислил, че ще има проблем със сигурността при mallocing! - person SHH; 06.11.2011
comment
Това е фино. Когато ОС ви дава памет, тя може да е била освободена от друг процес. Така че тази памет може да съдържа чувствителна информация като парола. Така че, за да ви попречи да четете такива данни, операционната система ще ги нулира, преди да ви ги даде. Но това е детайл на изпълнението и може да е различен, като например в някои вградени системи. - person Mysticial; 06.11.2011
comment
Това е малко встрани от въпроса на OP, но едно следствие от този ефект е, че винаги трябва да използвате calloc, а не malloc+memset, когато искате памет с нулева инициализация (поне за големи блокове, където времето до нула може да има значение). malloc+memset винаги ще има големи разходи за запис в целия блок, но calloc на системата може да се възползва от факта, че новата анонимна памет ще бъде запълнена с нула като начало. - person R.. GitHub STOP HELPING ICE; 07.11.2011
comment
@R.. Е, операционната система все пак не изпълнява ли същия нулев цикъл? Кога предварително написана страница ще бъде нулирана като част от free? Или има хардуерна поддръжка за това? - person Peter; 07.11.2011
comment
Отговорите в този въпрос може да ви помогнат да разберете това. Ядрото може да мами с calloc, като всъщност не изписва всички занулени страници, докато не бъдат използвани. Memset (очевидно) принуждава страниците да бъдат изписани незабавно. Повече информация на линка. - person thomasrutter; 07.11.2011
comment
@Mysticial това всъщност е погрешна и вредна информация. Това не зависи от вградена система или нещо подобно, а от реализацията на libc и как тя използва повторно паметта след освобождаване и дали предишно освобождаване се е случило в приложението. Ключова дума: кеш на страници (malloc's). - person mirabilos; 01.04.2014
comment
@mirabilos Казах, че може да е различно. Не съм казал, че е. Споменах само вградени, защото там е най-вероятно да сте в затворена среда, изключена от външния свят, където сигурността няма да има значение. В такива случаи няма нужда операционната система или изпълнението на libc да нулират паметта. - person Mysticial; 01.04.2014
comment
@Mysticial: mayyyyybe… но все пак, вашият коментар може да бъде приет от неизмитите маси като смисъл, че не ви пука за „някои вградени системи, на които моят код така или иначе никога няма да работи“… - person mirabilos; 01.04.2014

Операционната система обикновено ще изчисти новите страници от паметта, които изпраща към вашия процес, така че да не може да разглежда данните на по-стар процес. Това означава, че първия път, когато инициализирате променлива (или malloc нещо), тя често ще бъде нула, но ако някога използвате повторно тази памет (като я освободите и malloc-ing отново, например), тогава всички залози са изключени.

Това несъответствие е точно причината неинициализираните променливи да са толкова трудни за намиране грешки.


Що се отнася до нежеланото натоварване на производителността, избягването на неопределено поведение вероятно е по-важно. Каквото и малко увеличение на производителността, което бихте могли да постигнете в този случай, няма да компенсира трудно откриваемите грешки, с които ще трябва да се справите, ако някой леко модифицира кодовете (нарушавайки предишни предположения) или го пренесе към друга система (където предположенията може да са били невалидни на първо място).

person hugomg    schedule 06.11.2011
comment
+1 ... не съм сигурен дали вероятно се изисква в мисълта за удебеления текст ;-) - person ; 06.11.2011

Защо предполагате, че malloc() се инициализира на нула? Случва се така, че първото извикване на malloc() води до извикване на sbrk или mmap системни извиквания, които разпределят страница памет от ОС. Операционната система е длъжна да предостави памет с нулева инициализация от съображения за сигурност (в противен случай данните от други процеси стават видими!). Така че може да си помислите там - ОС губи време да нулира страницата. Но не! В Linux има специална сингълтън страница за цялата система, наречена „нулева страница“ и тази страница ще бъде картографирана като Copy-On-Write, което означава, че само когато действително пишете на тази страница, ОС ще разпредели друга страница и инициализирайте го. Така че се надявам това да отговори на въпроса ви относно производителността. Моделът за страниране на паметта позволява използването на паметта да бъде някак мързеливо, като поддържа способността за многократно картографиране на една и съща страница плюс способността да се справя със случая, когато се случи първото записване.

Ако извикате free(), разпределителят glibc ще върне региона в неговите безплатни списъци и когато malloc() бъде извикан отново, може да получите същия регион, но замърсен с предишните данни. В крайна сметка free() може да върне паметта на операционната система чрез повторно извикване на системни повиквания.

Забележете, че glibc man страницата на malloc() стриктно казва, че паметта не се изчиства, така че чрез „договора“ на API не можете да приемете, че тя се изчиства. Ето оригиналния откъс:

malloc() разпределя size байтове и връща указател към разпределената памет.
Паметта не е изчистена. Ако размерът е 0, тогава malloc() връща или NULL, или уникална стойност на указател, която по-късно може успешно да бъде предадена на free().

Ако искате, можете да прочетете повече за тази документация, ако се притеснявате за производителността или други странични ефекти.

person Dan Aloni    schedule 06.11.2011

Промених вашия пример, за да съдържа 2 идентични разпределения. Сега е лесно да се види, че malloc не нула инициализира паметта.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    {
      double *a = malloc(sizeof(double)*100);
      *a = 100;
      printf("%f\n", *a);
      free(a);
    }
    {
      double *a = malloc(sizeof(double)*100);
      printf("%f\n", *a);
      free(a);
    }

    return 0;
}

Изход с gcc 4.3.4

100.000000
100.000000
person Praetorian    schedule 06.11.2011
comment
Опитах това, което направихте вие ​​и ако разпределя само 100 байта тогава, въпреки че указателят сочи към СЪЩИЯ адрес, стойността на този адрес е различна. Ако заделя 400 байта или повече, тогава стойността на указателя и стойността в паметта са еднакви. Каква според вас може да е причината? - person yoyo_fun; 07.09.2017

От gnu.org:

Много големи блокове (много по-големи от страница) се разпределят с mmap (анонимно или чрез /dev/zero) от тази реализация.

person TomaszK    schedule 06.11.2011
comment
OP обаче се развива на малки стъпки. Тази препратка, която намерихте, също има ли нещо за това? - person hugomg; 06.11.2011

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

person Community    schedule 06.11.2011

Вашият код не демонстрира, че malloc инициализира паметта си на 0. Това може да бъде направено от операционната система, преди програмата да стартира. За да видите shich, запишете различна стойност в паметта, освободете я и извикайте malloc отново. Вероятно ще получите същия адрес, но ще трябва да проверите това. Ако е така, можете да погледнете какво съдържа. Информирай ни!

person TonyK    schedule 06.11.2011

Знаете ли, че определено се инициализира? Възможно ли е областта, върната от malloc() просто често да има 0 в началото?

person Community    schedule 06.11.2011

Никога никога не разчитайте на който и да е компилатор за генериране на код, който ще инициализира паметта за нещо. malloc просто връща указател към n байта памет някъде по дяволите може дори да е в swap.

Ако съдържанието на паметта е критично, инициализирайте го сами.

person FlyingGuy    schedule 06.11.2011
comment
Освен в случаите, когато езикът гарантира, че ще бъде инициализиран. Статичните обекти без изрична инициализация имплицитно се инициализират до нула. - person Keith Thompson; 30.12.2011

malloc не инициализира паметта до нула. Връща ви го такъв, какъвто е, без да докосва паметта или да променя стойността му.

И така, защо получаваме тези нули?

Преди да отговорим на този въпрос, трябва да разберем как работи malloc:

Когато извикате malloc, той проверява дали разпределителят glibc има памет с искания размер или не.

Ако го направи, той ще ви върне тази памет. Тази памет обикновено се дължи на предишна операция free, така че има ненужна стойност(може би нула или не) в повечето случаи.

От друга страна, ако не може да намери памет, ще помоли ОС да разпредели памет за него, като извика sbrk или mmap системни извиквания. ОС връща страница с нулева инициализация от съображения за сигурност, тъй като тази памет може да е била използвана от друг процес и носи ценна информация като пароли или лични данни.

Можете сами да прочетете за това от тази Връзка:

Съседни парчета могат да бъдат обединени на свободен без значение какъв е техният размер. Това прави внедряването подходящо за всички видове модели на разпределение, без като цяло да води до големи загуби на памет чрез фрагментация.

Много големи блокове (много по-големи от страница) се разпределят с mmap (анонимно или чрез /dev/zero) от тази реализация

В някои реализации calloc използва това свойство на ОС и иска от ОС да разпредели страници за него, за да се увери, че паметта е винаги инициализира се с нула, без да се инициализира самият той.

person Mostafa Wael    schedule 08.04.2021