Почему 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);
}

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

9 ответов


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

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


Ответ:

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

  1. он перерабатывает память, которая была ранее выделена и освобождена от того же процесса.
  2. он просит новую страницу(ы) из операционная система.

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

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

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

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

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


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

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


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

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

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

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


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

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


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


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

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

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

malloc() выделяет size байт и возвращает указатель на выделенную память.
Память не очищается. Если 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

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


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


от gnu.org:

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


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


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

Если содержимое памяти данные инициализировать самому.