Каллок лучше, чем маллок?

Я только что узнал о функции C calloc() на днях. Прочитав его описание и чем он отличается от malloc (1, 2), я понимаю, что как программист без встроенных программ мне следует всегда использовать calloc() . Но так ли это на самом деле?

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

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


person Violet Giraffe    schedule 21.12.2016    source источник
comment
В C + почему вы хотите calloc()?   -  person Sourav Ghosh    schedule 21.12.2016
comment
C или C ++? При ручном выделении памяти C ++ не одобряются функции * alloc.   -  person NathanOliver    schedule 21.12.2016
comment
Общее правило состоит в том, что если у вас есть проблема P, вы используете решение X, которое решает P, а не Y, которое решает P как побочный эффект. Нет замены критическому мышлению; программирование по книге рецептов работает редко.   -  person Kerrek SB    schedule 21.12.2016
comment
Если это для памяти, вам не нужна инициализация нулями, зачем вообще доплачивать за нее?   -  person StoryTeller - Unslander Monica    schedule 21.12.2016
comment
Как calloc сломать любую программу, которая нормально работает с malloc? Каждый, кто использует malloc, должен знать, что он не может полагаться на содержимое буфера. Если это все нули, это всего лишь один из возможных результатов случайного содержимого.   -  person Gerhardh    schedule 21.12.2016
comment
@NathanOliver: Я программист на C ++, подумал, что еще добавлю тег C, потому что вопрос касается C функций. Я хорошо осведомлен о передовых методах управления памятью C ++, но есть один случай, когда я чувствую malloc лучший способ выделить память: когда мне нужно выделить буфер фиксированного размера, который инкапсулирован в одном из моих классов.   -  person Violet Giraffe    schedule 21.12.2016
comment
@VioletGiraffe - Вам придется запустить его, чтобы увидеть достоинства malloc. Я просто использую ::operator new, если мне действительно нужно.   -  person StoryTeller - Unslander Monica    schedule 21.12.2016
comment
@VioletGiraffe Если вам нужен буфер фиксированного размера, std::unique_ptr<T[]> я бы пошел по пути. Теперь у вас есть встроенное управление памятью.   -  person NathanOliver    schedule 21.12.2016
comment
@NathanOliver: и накладные расходы времени выполнения. Но да, я понял.   -  person Violet Giraffe    schedule 21.12.2016
comment
@VioletGiraffe Какие накладные расходы времени выполнения? Если у вас есть назначенный вручную указатель, вам все равно придется удалить его в какой-то момент. std::unique_ptr просто связывает это удаление с областью действия указателей, поэтому вам не нужно об этом помнить. std::unique_ptr со средством удаления по умолчанию должно быть столь же эффективным, как и выполнение его самостоятельно, с тем преимуществом, что на самом деле нельзя ошибиться.   -  person NathanOliver    schedule 21.12.2016
comment
@NathanOliver: Нет ли дополнительных накладных расходов в уникальном указателе? Никаких сервисных структур, никаких поточно-защитных механизмов? Мне трудно поверить, что это просто class unique_ptr {T* _ptr};   -  person Violet Giraffe    schedule 21.12.2016
comment
@NathanOliver: В интересах полного раскрытия информации стоит взглянуть на фактическую стоимость времени выполнения, которую unique_ptr имеет по необработанным указателям: средство для динамической передачи прав собственности имеет динамическую стоимость, а именно что-то вроде p = make_unique<...>(...); необходимо сначала выполнить нулевую проверку p, даже если вы уже статически знаете, что оно равно нулю. Более того, передача уникальных указателей через вызовы функций уступает Itanium ABI, потому что объект должен помещаться в стек, а необработанный указатель передается в регистре. (Это ошибка реализации, а не ошибка дизайна, но все же.)   -  person Kerrek SB    schedule 21.12.2016
comment
@VioletGiraffe Это очень похоже на то, что есть у вас. Здесь нет потоковой безопасности, нет атомарного подсчета ссылок, нет нулевых проверок, он должен быть как можно более легким. Он действительно хранит средство удаления в структуре, но благодаря оптимизации пустого базового класса, если это просто пустой средство удаления, такое как вызов delete[], то это даже не увеличивает размер указателя. В gcc sizeof(std::unique_ptr<int[]>) это 8, который имеет тот же размер, что и одиночный указатель (64 бита).   -  person NathanOliver    schedule 21.12.2016
comment
@NathanOliver: Конечно, все это очень редко бывает значительным по сравнению с огромным концептуальным преимуществом, которое дает нам семантика владения на основе типов.   -  person Kerrek SB    schedule 21.12.2016
comment
@KerrekSB Спасибо за это. Я забыл об этом случае.   -  person NathanOliver    schedule 21.12.2016
comment
@NathanOliver: спасибо, это хорошо.   -  person Violet Giraffe    schedule 21.12.2016
comment
Вы не заботитесь о производительности, пока не узнаете, где находится ваше узкое место.   -  person n. 1.8e9-where's-my-share m.    schedule 21.12.2016
comment
@ n.m: такой подход часто приводит к тому, что программа представляет собой одно большое узкое место в целом, возможно, без четко определенных горячих точек. Сказав это - да, ранняя оптимизация - зло.   -  person Violet Giraffe    schedule 21.12.2016


Ответы (5)


Это действительно зависит от ситуации. Практическое правило

  • Если вы сначала записываете в выделенную память, лучше использовать malloc() (меньше возможных накладных расходов).

    Пример: рассмотрим следующий сценарий

    char * pointer = NULL;
    
    //allocation
    
    strcpy(pointer, source);
    

    здесь можно очень хорошо использовать malloc().

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

    Пример:

    char * pointer = NULL;
    
    //allocation
    
    strcat(pointer, source);
    

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

Чтобы разработать второй сценарий, цитируя C11, глава §7.24.3.1 (следуйте моему выделению)

Функция strcat() добавляет копию строки, на которую указывает s2 (включая завершающий нулевой символ), в конец строки, на которую указывает s1. Начальный символ s2 заменяет нулевой символ в конце s1. [....]

Итак, в этом случае указатель назначения должен быть указателем на строку. Распределение через calloc() гарантирует, что при выделении с использованием malloc() не может гарантировать, что, как мы знаем из главы §7.22.3.4

Функция malloc выделяет пространство для объекта, размер которого определяется параметрами size и , значение которого не определено.


РЕДАКТИРОВАТЬ:

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

person Sourav Ghosh    schedule 21.12.2016
comment
Аспект нулевой инициализации довольно ясен. Но как насчет ленивого выделения памяти, которое обеспечивает calloc()? - person Violet Giraffe; 21.12.2016
comment
@VioletGiraffe AFAIK, это свойство распределителя памяти и одинаково применимо с malloc() и / или calloc() для конкретной платформы. Вы можете уточнить? - person Sourav Ghosh; 21.12.2016
comment
Ссылки в моем вопросе: stackoverflow.com/a/2688522/634821 и vorpus.org/blog/why-does-calloc-exist - person Violet Giraffe; 21.12.2016
comment
Если вам нужно, чтобы первый символ в массиве char был '\0', просто сделайте это. calloc - это перебор для такой простой инициализации. - person Pete Becker; 21.12.2016
comment
@PeteBecker, это был один пример сценария, мы наверняка можем найти более сложные сценарии, где нам нужно инициализировать всю память. :) - person Sourav Ghosh; 21.12.2016

Основное различие между malloc и calloc состоит в том, что calloc инициализирует ваш буфер нулями, а malloc оставит память неинициализированной.

Это приводит к общей программной идиоме «не платите за то, что не используете». Другими словами, зачем обнулять что-то (за что нужно платить), если в этом нет необходимости (пока)?

В качестве побочного примечания, поскольку вы отметили C ++: использование памяти вручную с использованием new/delete не одобряется в современном C ++ (за исключением редких случаев пулов памяти и т. Д.). malloc/free используется еще реже, и его следует использовать очень экономно.

person Cory Kramer    schedule 21.12.2016

Используйте calloc для распределений с нулевым заполнением, но только тогда, когда заполнение нулями действительно необходимо.

Вы всегда должны использовать calloc(count,size) вместо buff=malloc(total_size); memset(buff,0,total_size).

Призыв к нулю-memset - это ключ. И malloc, и calloc транслируются в вызовы ОС, которые выполняют множество оптимизаций, по возможности используют аппаратные уловки и т. Д. Но ОС мало что может сделать с memset.

С другой стороны, когда вам нужно обнулить выделенную память? Единственное распространенное использование - это элементы произвольной длины с нулевым концом, такие как C-строки. Если это так, конечно, используйте calloc.

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

Предположим, вы написали свой настраиваемый связанный список и решили пропустить обнуление указателя на следующий узел, выделив память для узла с помощью calloc. Он работает нормально, затем кто-то использует его с настраиваемым размещением new, которое не заполняется нулями. Проблема в том, что иногда он будет заполнен нулями и может пройти все обычные тесты, поступить в производство, и там он выйдет из строя, иногда выйдет из строя, ужасная неповторимая ошибка.

Для целей отладки заполнение нулями обычно тоже не очень хорошо. 0 слишком распространено, вы редко можете написать что-то вроде assert(size);, потому что обычно это действительное значение, вы обрабатываете его с помощью if(!size), а не с помощью утверждений. В отладчике это тоже не бросается в глаза, в памяти обычно везде нули. Лучше всего избегать беззнаковых типов для длин (подписанные длины могут быть полезны для обработки ошибок во время выполнения, а также для некоторых наиболее распространенных проверок переполнения). Итак, хотя buff=malloc(total_size); memset(buff,0,total_size) следует избегать, следующее нормально:

const signed char UNINIT_MEM=MY_SENTINEL_VALUE;
buff=malloc(total_size);
#if DEBUG_MEMORY
memset(buff,UNINIT_MEM,total_size);
#endif

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

person Mike Tyukanov    schedule 22.12.2016

malloc () гораздо чаще встречается в коде C, чем calloc ().

При текстовом поиске "malloc" вызовы calloc () будут пропущены.

Сменные библиотеки часто имеют mymalloc (), myrealloc (), myfree (), но не mycalloc ().

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

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

person Malcolm McLean    schedule 22.12.2016

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

person Paul Evans    schedule 21.12.2016
comment
Меня больше интересует ленивое распределение, которое предоставляет calloc(). - person Violet Giraffe; 21.12.2016
comment
@VioletGiraffe Тогда тебе определенно нужен malloc. В Linux malloc возвращает указатель, даже не указывающий на реальную память. Память становится реальной, когда программа обращается к ней в первый раз. - person Paul Evans; 21.12.2016
comment
@VioletGiraffe calloc () пока не выполняет ленивое выделение памяти ни на одной из распространенных платформ. Однако malloc () делает. - person nos; 21.12.2016
comment
@nos: у меня есть ссылки на две статьи (ну, статья и ответ SO) в моем вопросе, в которых указано иное. Они ложны? - person Violet Giraffe; 21.12.2016