Разница между malloc и calloc?

780

В чем разница между выполнением:

ptr = (char **) malloc (MAXELEMS * sizeof(char *));

или:

ptr = (char **) calloc (MAXELEMS, sizeof(char*));

Когда стоит использовать calloc вместо malloc или наоборот?

user105033
источник
32
В Си вы неmalloc
разыгрываете
8
В C вы могли бы написать выше в более общем виде, как:ptr = calloc(MAXELEMS, sizeof(*ptr));
chqrlie
7
Интересный пост о разнице между calloc и malloc + memset vorpus.org/blog/why-does-calloc-exist
ddddavidee
2
@ddddavidee Я тоже нашел этот блог после того, как был недоволен таким количеством ответов в сети. Натаниэль Дж. Смит заслуживает более 100 баллов за свой анализ.
lifebalance

Ответы:

851

calloc()дает нулевой инициализированный буфер, а malloc()память остается неинициализированной.

Для больших выделений большинство callocреализаций в основных операционных системах получат страницы с нулевым значением из операционной системы (например, через POSIX mmap(MAP_ANONYMOUS)или Windows VirtualAlloc), поэтому им не нужно записывать их в пространстве пользователя. Так обычно mallocполучается больше страниц из ОС; callocпросто пользуется гарантией ОС.

Это означает, что callocпамять все еще может быть «чистой» и лениво распределенной, а копирование при записи сопоставляется с общесистемной общей физической страницей нулей. (Предполагая систему с виртуальной памятью.)

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

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


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

В встроенном Linux может использоваться malloc mmap(MAP_UNINITIALIZED|MAP_ANONYMOUS), который включен только для некоторых встроенных ядер, поскольку небезопасен в многопользовательской системе.

Фред Ларсон
источник
224
Варианты * alloc являются довольно мнемоническими - clear-alloc, memory-alloc, re-alloc.
Каскабель
43
Используйте malloc (), если вы собираетесь установить все, что вы используете в выделенном пространстве. Используйте calloc (), если вы собираетесь оставить части данных неинициализированными, и было бы полезно обнулить ненастроенные части.
Джонатан Леффлер
268
callocне обязательно дороже, так как ОС может сделать некоторые трюки, чтобы ускорить его. Я знаю, что FreeBSD, когда он получает какое-то время простоя ЦП, использует его для запуска простого процесса, который просто обходит и обнуляет освобожденные блоки памяти и помечает блоки, таким образом, процессами с флагом. Поэтому, когда вы это сделаете calloc, он сначала попытается найти один из таких предварительно обнуленных блоков и просто дать его вам - и, скорее всего, он его найдет.
Павел Минаев
28
Я склонен считать, что если ваш код становится «более безопасным» в результате нулевого выделения ресурсов по умолчанию, то ваш код недостаточно безопасен, независимо от того, используете ли вы malloc или calloc. Использование malloc является хорошим индикатором того, что данные должны быть инициализированы - я использую calloc только в тех случаях, когда эти 0 байтов действительно значимы. Также обратите внимание, что calloc не обязательно делает то, что вы думаете для не-char типов. Никто на самом деле больше не использует представления ловушек или плавающие не-IEEE, но это не повод думать, что ваш код действительно переносим, ​​когда это не так.
Стив Джессоп
18
@SteveJessop "Безопаснее" не правильное слово. Я думаю, что «детерминист» - лучший термин. Код, который является более детерминированным, чем наличие сбоев, зависящих от синхронизации и последовательности данных, будет легче изолировать сбои. Calloc - иногда простой способ получить этот детерминизм по сравнению с явной инициализацией.
Деннис
362

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

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

См., Например, этот вопрос SO для дальнейшего обсуждения поведения malloc

Исак Саво
источник
49
callocне нужно писать нули. Если выделенный блок состоит в основном из новых нулевых страниц, предоставляемых операционной системой, он может оставить их нетронутыми. Это, конечно, требует callocнастройки операционной системы, а не общей библиотечной функции поверх malloc. Или разработчик может callocсравнить каждое слово с нулем, прежде чем обнулять его. Это не сэкономит время, но предотвратит загрязнение новых страниц.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
3
@R .. интересная заметка. Но на практике, такие реализации существуют в дикой природе?
Исак Саво
10
Все dlmallocподобные реализации пропускают, memsetесли чанк был получен с помощью mmapновых анонимных страниц (или эквивалентных). Обычно этот вид распределения используется для больших кусков, начиная с 256 КБ или около того. Я не знаю ни одной реализации, которая делает сравнение с нулем, прежде чем писать ноль, кроме моей собственной.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
1
omallocтакже пропускает memset; callocне нужно прикасаться к страницам, которые еще не используются приложением (кеш страниц), никогда. Хотя, чрезвычайно примитивные callocреализации отличаются.
Мирабилось
10
Calloc в glibc проверяет, получает ли она свежую память от ОС. Если это так, он знает, что не нужно его писать, потому что mmap (..., MAP_ANONYMOUS) возвращает память, которая уже обнулена.
Питер Кордес
112

Одним из часто упускаемых из виду преимуществ callocявляется то, что (совместимые реализации) это поможет защитить вас от целочисленных уязвимостей переполнения. Для сравнения:

size_t count = get_int32(file);
struct foo *bar = malloc(count * sizeof *bar);

против

size_t count = get_int32(file);
struct foo *bar = calloc(count, sizeof *bar);

Первое может привести к крошечному выделению и последующим переполнениям буфера, если countоно больше, чем SIZE_MAX/sizeof *bar. Последний автоматически потерпит неудачу в этом случае, так как большой объект не может быть создан.

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

R .. GitHub СТОП, ПОМОГАЯ ЛЬДУ
источник
17
По-видимому, именно арифметическое переполнение стало причиной дыры в OpenSSH в 2002 году. Хорошая статья из OpenBSD об опасностях этого с функциями, связанными с памятью: undeadly.org/cgi?action=article&sid=20060330071917
Филипп П.
4
@ KomradeP .: Интересно. К сожалению, статья, на которую вы ссылаетесь, имеет дезинформацию в самом начале. Пример с charявляется не переполнением, а преобразованием, определяемым реализацией, при присваивании результата обратно в charобъект.
R .. GitHub ОСТАНОВИТЬСЯ, ПОМОГАЯ ЛЬДУ
Это возможно только для иллюстрации. Потому что компилятор все равно может оптимизировать это. Мой компилируется в этот ассм: push 1.
Филипп П.
1
@tristopia: Дело не в том, что код можно использовать во всех реализациях, а в том, что он некорректен без дополнительных предположений и, следовательно, не корректно / переносимо.
R .. GitHub ОСТАНОВИТЬСЯ, ПОМОГАЯ ЛЬДУ
3
@tristopia: Если ваш образ мышления « size_t64-битный, так что это не проблема», это ошибочный способ мышления, который может привести к ошибкам безопасности. size_tявляется абстрактным типом, который представляет размеры, и нет никаких оснований считать, что произвольный продукт 32-битного числа и size_t( sizeof *barв принципе, может быть больше 2 ^ 32 в 64-битной реализации C!) подходит size_t.
R .. GitHub ОСТАНОВИТЬСЯ, ПОМОГАЯ ЛЕДОМ
37

Документация делает calloc похожим на malloc, который просто инициализирует память нулем; это не основная разница! Идея calloc состоит в том, чтобы избежать семантики копирования при записи для выделения памяти. Когда вы выделяете память с помощью calloc, все это отображается на одной физической странице, которая инициализируется нулем. Когда любая из страниц выделенной памяти записывается в физическую страницу, выделяется. Это часто используется для создания ОГРОМНЫХ хеш-таблиц, например, поскольку пустые части хеш-функции не поддерживаются какой-либо дополнительной памятью (страницами); они с радостью указывают на единственную инициализированную нулями страницу, которая может быть даже разделена между процессами.

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

Вот одна история оптимизации по этой теме: http://blogs.fau.de/hager/2007/05/08/benchmarking-fun-with-calloc-and-zero-pages/

t0rakka
источник
26

Там нет разницы в размере выделенного блока памяти. callocпросто заполняет блок памяти физическим шаблоном "все нули". На практике часто предполагается, что объекты, расположенные в блоке памяти, выделенном с, callocимеют начальное значение, как если бы они были инициализированы литералом 0, то есть целые числа должны иметь значение 0, переменные с плавающей точкой - значение 0.0, указатели - соответствующее значение нулевого указателя , и так далее.

С педантичной точки зрения, тем не менее, calloc(как и memset(..., 0, ...)) гарантируется только правильная инициализация (с нулями) объектов типа unsigned char. Все остальное не гарантируется должной инициализацией и может содержать так называемое представление ловушек , которое вызывает неопределенное поведение. Другими словами, для любого типа, отличного unsigned charот вышеупомянутой схемы, состоящей из всех нулей, может представлять собой недопустимое значение, представление прерывания.

Позднее в одном из стандартов Технических исправлений к С99 поведение было определено для всех целочисленных типов (что имеет смысл). Т.е. формально в текущем языке Си вы можете инициализировать только целочисленные типы с callocmemset(..., 0, ...)). Использование его для инициализации чего-либо еще в общем случае приводит к неопределенному поведению с точки зрения языка Си.

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

Наконец, еще одна важная деталь - это то, что callocтребуется для внутреннего расчета окончательного размера блока путем умножения размера элемента на количество элементов. При этом callocнеобходимо следить за возможным арифметическим переполнением. Это приведет к неудачному распределению (нулевой указатель), если запрошенный размер блока не может быть правильно рассчитан. Между тем, ваша mallocверсия не пытается следить за переполнением. Он выделит некоторый «непредсказуемый» объем памяти в случае переполнения.

Муравей
источник
Согласно параграфу «еще одна важная деталь»: кажется, это memset(p, v, n * sizeof type);создает проблему, потому что n * sizeof typeможет переполниться. Думаю, мне нужно использовать for(i=0;i<n;i++) p[i]=v;цикл для надежного кода.
chux - Восстановить Монику
Было бы полезно, если бы существовали стандартные средства, с помощью которых код мог бы утверждать, что реализация должна использовать все биты-ноль в качестве нулевого указателя (иначе отказывается от компиляции), поскольку существуют реализации, которые используют другие представления нулевого указателя, но они сравнительно редко; код, который не должен выполняться в таких реализациях, может быть быстрее, если он может использовать calloc () или memset для инициализации массивов указателей.
суперкат
@chux Нет, если существует массив с nэлементами, где элемент имеет размер sizeof type, то он n*sizeof typeне может переполниться, поскольку максимальный размер любого объекта должен быть меньше SIZE_MAX.
12431234123412341234123
@ 12431234123412341234123 Правда о размере массива <= SIZE_MAX, но здесь нет никаких массивов . Возвращенный указатель calloc()может указывать на выделенную память, чем превышает SIZE_MAX. Многие реализации ограничивают произведение двух аргументов calloc()до SIZE_MAX, но спецификация C не устанавливает этого ограничения.
chux - Восстановить Монику
21

из статьи Бенчмаркинг забавы с calloc () и нулевых страниц на блоге Georg Hager в

При выделении памяти с помощью calloc () объем запрошенной памяти выделяется не сразу. Вместо этого все страницы, которые принадлежат блоку памяти, связаны с одной страницей, содержащей все нули, с помощью некоторой магии MMU (ссылки ниже). Если такие страницы только для чтения (что было верно для массивов b, c и d в исходной версии эталонного теста), данные предоставляются с одной нулевой страницы, которая, конечно же, помещается в кэш. Так много для связанных с памятью ядер циклов. Если страница записывается (независимо от того, как), происходит сбой, отображается «настоящая» страница, а нулевая страница копируется в память. Это называется копированием при записи, хорошо известным подходом к оптимизации (который я даже несколько раз преподавал в своих лекциях по C ++). После того,

Ашиш Чаван
источник
где ссылка?
Рупеш Ядав.
2
Первая строка ответа содержит ссылку на блог Георга Хагера.
Ашиш
11

callocкак правило, malloc+memsetдо 0

Обычно немного лучше использовать malloc+memsetявно, особенно когда вы делаете что-то вроде:

ptr=malloc(sizeof(Item));
memset(ptr, 0, sizeof(Item));

Это лучше, потому что sizeof(Item)это известно компилятору во время компиляции, и компилятор в большинстве случаев заменит его наилучшими инструкциями для обнуления памяти. С другой стороны, если memsetпроисходит в calloc, размер параметра выделения не скомпилирован в callocкоде, и memsetчасто вызывается real , который обычно содержит код, который выполняет побайтовое заполнение до длинной границы, чем цикл для заполнения sizeof(long)заполнение памяти кусками и, наконец, побайтовое заполнение оставшегося пространства. Даже если распределитель достаточно умен, чтобы вызывать aligned_memsetего, он все равно будет универсальным циклом.

Одно заметное исключение может произойти, когда вы выполняете malloc / calloc для очень большого куска памяти (некоторые power_of_two килобайта), в этом случае выделение может быть сделано непосредственно из ядра. Поскольку ядра ОС обычно обнуляют всю память, которую они отдают по соображениям безопасности, достаточно умный calloc может просто вернуть ее с дополнительным обнулением. Опять же - если вы просто выделяете что-то, что, как вы знаете, мало, вам может быть лучше с malloc + memset с точки зрения производительности.

virco
источник
+1 за напоминание о том, что общая реализация функциональности в системной библиотеке не обязательно быстрее, чем та же операция в пользовательском коде.
Патрик Шлютер
1
Есть также второй момент, который делает calloc()медленнее, чем malloc(): умножение на размер. calloc()требуется использовать общее умножение (если size_tэто 64-битные, даже очень дорогие операции 64-битные * 64-битные = 64-битные), тогда как malloc () часто будет иметь постоянную времени компиляции.
Патрик Шлютер
4
У glibc calloc есть некоторые умения, чтобы решить, как наиболее эффективно очистить возвращенный кусок, например, иногда только его часть нуждается в очистке, а также развернутый очистить до 9 * sizeof (size_t). Память - это память, очистка ее 3 байта за раз не будет быстрее только потому, что вы собираетесь использовать ее для хранения struct foo { char a,b,c; };. callocвсегда лучше, чем malloc+ memset, если вы всегда собираетесь очистить весь mallocрегион ed. callocимеет тщательную, но эффективную проверку на переполнение int в элементах размера *.
Питер Кордес
8

Разница 1:

malloc() Обычно выделяется блок памяти и инициализируется сегмент памяти.

calloc() выделяет блок памяти и инициализирует весь блок памяти в 0.

Разница 2:

Если вы учитываете malloc()синтаксис, он будет принимать только 1 аргумент. Рассмотрим следующий пример ниже:

data_type ptr = (cast_type *)malloc( sizeof(data_type)*no_of_blocks );

Пример: если вы хотите выделить 10 блоков памяти для типа int,

int *ptr = (int *) malloc(sizeof(int) * 10 );

Если вы считаете calloc()синтаксис, он будет принимать 2 аргумента. Рассмотрим следующий пример ниже:

data_type ptr = (cast_type *)calloc(no_of_blocks, (sizeof(data_type)));

Пример: если вы хотите выделить 10 блоков памяти для типа int и инициализировать все это в ZERO,

int *ptr = (int *) calloc(10, (sizeof(int)));

Сходство:

Оба malloc()и calloc()вернут void * по умолчанию, если они не приводятся по типу.!

Шиварадж Бхат
источник
И почему вы сохраняете data_type и cast_type разными?
Распродано
7

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

  • calloc()выделяет область памяти, длина будет равна произведению ее параметров. callocзаполняет память нулями и возвращает указатель на первый байт. Если ему не удается найти достаточно места, он возвращает NULLуказатель.

Синтаксис: ptr_var=(cast_type *)calloc(no_of_blocks , size_of_each_block); т.е.ptr_var=(type *)calloc(n,s);

  • malloc()выделяет один блок памяти REQUSTED SIZE и возвращает указатель на первый байт. Если он не может найти требуемое количество памяти, он возвращает нулевой указатель.

Синтаксис: функция принимать один аргумент, который является количеством байт для распределения, в то время как функция принимает два аргумента, один из которых числа элементов, а другое количество байт для распределения для каждого из этих элементов. Также инициализирует выделенное пространство нулями, пока нет.ptr_var=(cast_type *)malloc(Size_in_bytes);malloc()calloc()calloc()malloc()

Jainendra
источник
6

calloc()Функция , которая объявлена в <stdlib.h>заголовке предлагает несколько преимуществ по сравнению с malloc()функцией.

  1. Он выделяет память как количество элементов заданного размера, и
  2. Он инициализирует память, которая выделена так, что все биты равны нулю.
Випин Дивакар
источник
6

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

Их прототипы следующие:

void *malloc( size_t n);
void *calloc( size_t n, size_t t)

Между ними в основном два различия:

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

  • Синтаксис: malloc()принимает 1 аргумент (размер, который должен быть выделен) и calloc()принимает два аргумента (количество блоков, которые должны быть выделены, и размер каждого блока).

Возвращаемое значение от обоих является указателем на выделенный блок памяти, если успешно. В противном случае будет возвращено значение NULL , указывающее на ошибку выделения памяти.

Пример:

int *arr;

// allocate memory for 10 integers with garbage values
arr = (int *)malloc(10 * sizeof(int)); 

// allocate memory for 10 integers and sets all of them to 0
arr = (int *)calloc(10, sizeof(int));

Та же функциональность, calloc()которую можно достичь с помощью malloc()и memset():

// allocate memory for 10 integers with garbage values   
arr= (int *)malloc(10 * sizeof(int));
// set all of them to 0
memset(arr, 0, 10 * sizeof(int)); 

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

elmiomar
источник
5

Разница еще не упомянута: ограничение по размеру

void *malloc(size_t size)можно выделить только до SIZE_MAX.

void *calloc(size_t nmemb, size_t size);может выделить до SIZE_MAX*SIZE_MAX.

Эта возможность не часто используется на многих платформах с линейной адресацией. Такие системы ограничивают calloc()с nmemb * size <= SIZE_MAX.

Рассмотрим тип вызываемых 512 байтов, disk_sectorи код хочет использовать много секторов. Здесь код может использовать только до SIZE_MAX/sizeof disk_sectorсекторов.

size_t count = SIZE_MAX/sizeof disk_sector;
disk_sector *p = malloc(count * sizeof *p);

Рассмотрим следующее, что позволяет выделять еще больше.

size_t count = something_in_the_range(SIZE_MAX/sizeof disk_sector + 1, SIZE_MAX)
disk_sector *p = calloc(count, sizeof *p);

Теперь, если такая система может обеспечить такое большое распределение, это другое дело. Большинство сегодня не будет. И все же это происходило в течение многих лет, когда SIZE_MAXбыло 65535. Учитывая закон Мура , подозреваю, что это произойдет около 2030 года с некоторыми моделями SIZE_MAX == 4294967295памяти с пулами памяти в 100 Гбайт.

Chux - Восстановить Монику
источник
2
Как правило, size_t может содержать размер самого большого объекта, который может обработать программа. Система, в которой size_t равен 32 битам, вряд ли сможет обрабатывать выделение, превышающее 4294967295 байт, и систему, которая будет в состоянии обрабатывать выделения, размер которых почти наверняка составит size_tболее 32 бит. Единственный вопрос заключается в том calloc, SIZE_MAXможно ли полагаться на использование со значениями, чей продукт превышает, чтобы получить ноль, а не возвращать указатель на меньшее распределение.
суперкат
Согласитесь с вашим обобщением , но спецификация C допускает calloc()превышение распределения SIZE_MAX. Это случалось в прошлом с 16-разрядной size_tверсией, и, поскольку память продолжает дешеветь, я не вижу причин, по которым это не может произойти, даже если это не распространено .
Чукс - Восстановить Монику
1
Стандарт C позволяет коду запрашивать распределение, размер которого превышает SIZE_MAX. Это, конечно, не требует наличия каких-либо обстоятельств, при которых такое распределение может быть успешным; Я не уверен, что есть какая-то особая выгода от того, что реализация, которая не может обрабатывать такое распределение, должна возвращаться NULL(особенно если учесть, что в некоторых реализациях обычно есть mallocуказатели возврата в пространство, которое еще не зафиксировано и может быть недоступно, когда код фактически пытается использовать). Это).
суперкат
Кроме того, там, где в прошлом могли существовать системы, у которых доступный диапазон адресов превышал наибольшее представимое целое число, я не вижу реальной возможности того, чтобы это когда-либо происходило снова, поскольку для этого потребовалась бы емкость в миллиарды гигабайт. Даже если бы закон Мура продолжал выполняться, переход от точки, в которой 32 бита перестало быть достаточным, к точке, в которой 64 бита перестало быть достаточным, потребовал бы вдвое больше времени, чем переход от точки, в которой 16 бит было достаточно, к точке, где 32 бита не было. «т.
суперкат
1
Почему реализация которых может вместить один выделение в избытке 4G не определяют size_tв uint64_t?
суперкат
2

Количество блоков:
malloc () назначает один блок запрошенной памяти,
calloc () назначает несколько блоков запрошенной памяти

Инициализация:
malloc () - не очищает и не инициализирует выделенную память.
calloc () - инициализирует выделенную память нулем.

Скорость:
malloc () быстро.
calloc () медленнее, чем malloc ().

Аргументы и синтаксис:
malloc () принимает 1 аргумент:

  1. байтов

    • Количество байтов для выделения

calloc () принимает 2 аргумента:

  1. длина

    • количество блоков памяти, которые будут выделены
  2. байтов
    • количество байтов, выделяемых в каждом блоке памяти
void *malloc(size_t bytes);         
void *calloc(size_t length, size_t bytes);      

Способ распределения памяти:
функция malloc назначает память нужного «размера» из доступной кучи.
Функция calloc назначает память, размер которой равен num * size.

Значение для имени:
имя malloc означает «распределение памяти».
Название calloc означает «непрерывное распределение».

Майбах Хабиб
источник