Почему 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 74

9 ответов:

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

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


Ответ:

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

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

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

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

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

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

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


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

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


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

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

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

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

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

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


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

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

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

заметил, что glibcна странице on 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

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

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

от gnu.org:

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

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

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

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