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


EDIT: О, мой предыдущий пример не был 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! (Никогда не думал об этом). Конечно, он должен инициализировать до нуля при назначении нового блока или большого блока.

Ответы

Ответ 1

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

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


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

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

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

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

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

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

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

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


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

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


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

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

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

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

Ответ 2

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

Это несоответствие именно поэтому неинициализированные переменные являются настолько трудными для поиска ошибок.


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

Ответ 3

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

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

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

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

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

Ответ 7

Из gnu.org:

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

Ответ 8

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

Ответ 9

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

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