Почему 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, даже если вы не хотите, чтобы это было иногда?


РЕДАКТИРОВАТЬ: О, мой предыдущий пример не был инициализировать, но случайно использовал "свежий" блок.

Что я точно искал, так это почему он инициализирует его, когда выделяет большой блок:

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)

Но спасибо, что указали, что есть причина БЕЗОПАСНОСТИ при неправильном обращении! (Никогда не думал об этом). Конечно, он должен инициализироваться в ноль при выделении нового блока или большого блока.

10 ответов

Решение

Короткий ответ:

Это не так, просто случается, что это ноль в вашем случае.
(Также ваш тестовый пример не показывает, что данные равны нулю. Он показывает только, если один элемент равен нулю.)


Длинный ответ:

Когда вы звоните malloc() произойдет одно из двух:

  1. Он перезаписывает память, которая была ранее выделена и освобождена из того же процесса.
  2. Он запрашивает новые страницы из операционной системы.

В первом случае память будет содержать данные, оставшиеся от предыдущих выделений. Так что это не будет ноль. Это обычный случай при выполнении небольших выделений.

Во втором случае память будет от ОС. Это происходит, когда программе не хватает памяти - или когда вы запрашиваете очень большое выделение. (как в вашем примере)

Вот подвох: память, поступающая от ОС, будет обнулена по соображениям безопасности. *

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

* Я отмечаю, что стандарт C ничего не говорит об этом. Это строго поведение ОС. Так что это обнуление может присутствовать или не присутствовать в системах, где безопасность не имеет значения.


Чтобы дать больше фона производительности для этого:

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


С другой стороны, это обнуление иногда является узким местом производительности. В некоторых числовых приложениях (таких как БПФ вне места) вам нужно выделить огромный кусок памяти. Используйте его для выполнения любого алгоритма, затем освободите его.

В этих случаях обнуление не является необходимым и составляет чистые накладные расходы.

Самый крайний пример, который я видел, - это 20-секундные накладные расходы на обнуление для 70-секундной операции с чистым буфером объемом 48 ГБ. (Примерно 30% накладных расходов.) (Конечно: машине не хватало пропускной способности памяти.)

Очевидное решение - просто повторно использовать память вручную. Но это часто требует прорыва через установленные интерфейсы. (особенно если это часть рутины библиотеки)

ОС обычно очищает свежие страницы памяти, которые она отправляет вашему процессу, поэтому она не может просматривать данные более старого процесса. Это означает, что в первый раз, когда вы инициализируете переменную (или что-то malloc), она часто будет равна нулю, но если вы когда-нибудь снова будете использовать эту память (например, путем ее освобождения и повторного размещения), все ставки будут отключены.

Именно из-за этого несоответствия так трудно найти неинициализированные переменные.


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

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

Если вы позвоните free(), glibc распределитель вернет регион в свои свободные списки, и когда malloc() вызывается снова, вы можете получить ту же область, но грязные с предыдущими данными. В конце концов, free() может вернуть память в ОС путем повторного вызова системных вызовов.

Обратите внимание, что glibc справочная страница на malloc() строго говорит, что память не очищается, поэтому, согласно "контракту" на API, вы не можете предполагать, что она действительно очищена. Вот оригинальная выдержка:

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

Если вы хотите, вы можете прочитать больше об этой документации, если вы беспокоитесь о производительности или других побочных эффектах.

Я изменил ваш пример, чтобы он содержал 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

С gnu.org:

В этой реализации очень большие блоки (намного больше, чем страница) выделяются с помощью mmap (анонимно или через / dev / zero).

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

Ваш код не демонстрирует это malloc Инициализирует свою память до 0. Это может быть сделано операционной системой до запуска программы. Чтобы понять, в чем дело, запишите в память другое значение, освободите его и снова вызовите malloc. Вы, вероятно, получите тот же адрес, но вам придется это проверить. Если это так, вы можете посмотреть, что в нем содержится. Дайте нам знать!

не инициализирует память до нуля . Он возвращает его вам в том виде, в каком он есть, не касаясь памяти и не меняя ее значения.

Итак, почему мы получаем эти нули?

Прежде чем ответить на этот вопрос, мы должны понять, как работает malloc :

Когда вы вызываете malloc, он проверяет, Распределитель имеет память требуемого размера или нет.

Если это так , он вернет вам это воспоминание. Это воспоминание обычно возникает из-за предыдущего операция, поэтому в большинстве случаев она имеет значение мусора (возможно, нулевое или нет).

С другой стороны, если он не может найти память , он попросит ОС выделить для него память , вызвав или же системные вызовы. ОС возвращает страницу с нулевой инициализацией по соображениям безопасности, поскольку эта память могла быть использована другим процессом и содержит ценную информацию, такую ​​как пароли или личные данные.

Вы можете сами прочитать об этом по этой ссылке:

Соседние фрагменты можно объединить на свободном участке независимо от их размера. Это делает реализацию подходящей для всех типов схем распределения, не вызывая больших потерь памяти из-за фрагментации.

Эта реализация выделяет очень большие блоки (намного больше страницы) с помощью mmap(анонимно или через / dev / zero).

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

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

Если содержимое памяти критично, инициализируйте его самостоятельно.

Вы знаете, что это определенно инициализируется? Возможно ли, что область, возвращаемая функцией malloc(), часто имеет 0 в начале?

Другие вопросы по тегам