2014-10-30 5 views
3

Как this answer on another question обложки, используя агрегатную инициализациюРазница между {0} и calloc?

struct foo { 
    size_t a; 
    size_t b; 
}; 

struct foo bar = {0}; 

результаты в встроенных типов инициализируются в ноль.

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

struct foo * bar2 = calloc(1, sizeof(struct foo)); 

оставляя в стороне тот факт, что одна переменная является указателем.
Глядя на отладчик, мы можем видеть, что оба значения a и b действительно равны нулю для обоих приведенных выше примеров.

В чем разница между двумя приведенными выше примерами, есть ли какие-либо проблемы или скрытые проблемы?

+0

Теперь мне интересно: что, если мы заменим 'calloc' на' alloca', а затем 'memset' на ноль? Тогда будет ли какая-то значительная разница? – MooseBoys

+0

'alloca' является нестандартным. Кроме того, 'alloca' выделяет в стеке, а не в куче, и не сообщает об ошибках (выделение большего объема памяти, чем доступно, имеет неопределенное поведение и, вероятно, приведет к сбою вашей программы, прежде чем она сможет обнаружить ошибку). –

+0

«он не сообщает об ошибках». Также не вызывает функцию, которая также может взорвать стек. Техническая бородавка на стандарте заключается в том, что она не гарантирует никаких гарантий на глубину стека, поэтому практически нет программы C строго соответствует. –

ответ

4

Да, есть существенная разница (помимо хранения класса вашего объекта типа struct foo):

struct foo bar = {0}; 
struct foo * bar2 = calloc(1, sizeof *bar2); 

Каждый член bar равен нулю инициализирован (и заполнение обнуляется для суб- объект без инициализатора, или если bar имеет static или thread_local хранения класса),
в то время как все *bar2 обнуляется, которые могут иметь совершенно разные результаты:

Нейт er null-pointers (T*)0, а числа с плавающей запятой со значением 0 гарантируют, что будут все-бит-0.
(На самом деле, только для char, unsigned char и signed char (а также некоторые из дополнительных точных-размера-типов из <stdint.h>) гарантируется, что все-бит-0 соответствует значению-0 до некоторого времени после C99. Позже технический исправления гарантировали его для всех интегральных типов.)

Формат с плавающей запятой может быть не IEEE754.

(В большинстве современных систем вы можете игнорировать эту возможность, хотя.)

Cite от c-faq (Спасибо Jim Балтер for linking it):

Серия Prime 50 используется сегмент 07777, смещение 0 для нулевой указатель, по крайней мере, для PL/I.

+1

Для 'struct foo bar = {0};', я не думаю, что есть какая-либо гарантия того, что заполнение обнулено. Вероятно, это будет так, потому что это просто проще реализовать, и почти наверняка не будет никакого дополнения в структуре, состоящей из двух членов 'size_t'. –

+0

@BillyONeal Я знаю, что это было до последнего редактирования, но не раньше того, который сказал «совершенно разные результаты: Ни нулевые указатели (T *) 0, ни числа с плавающей запятой со значением 0, биты-0 «. Так что мой вопрос стоит. –

+1

«В большинстве современных систем вы можете игнорировать эту возможность». - Да, хотя такие машины существовали и могут по-прежнему: http://c-faq.com/null/machexamp.html –

3

calloc дает вам кучу dynamically allocated нулевую зону памяти (в ваш bar2). Но автоматическая переменная (например, bar, при условии, что ее объявление находится внутри функции) назначается на call stack. См. Также calloc(3)

В C вам необходимо явно указать free выделенную памятью. Но данные, выделенные стекем, выворачиваются, когда возвращается функция.

Rerad также wikipage на C dynamic memory allocation, а также на garbage collection. Reference counting - широко используемый метод в C и C++, и его можно рассматривать как форму GC. Подумайте о circular references, с ними сложно справиться.

Boehm conservative GC может использоваться в программах на C.

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

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

Об инициализации: calloc указатель обнуляется (когда calloc успешно). Автоматическая переменная, инициализированная как {0}, также обнуляется. На практике некоторые реализации могут calloc по-разному большие объекты (путем запроса целых нулевых страниц из ядра для них, например, с mmap(2)) и небольших объектов (путем повторного использования, если доступно, ранее free-d зоны и обнуления). нулевая зона использует быстрый эквивалент memset(3)

PS. Я игнорирую странные машины, на которых вся нулевая бит-зона памяти не является очищенными данными для стандарта C, то есть {0}. Я не знаю таких машин на практике, даже если я знаю, что они в принципе возможны (и теоретически указатель NULL может не быть полностью нулевым битом)

BTW, компилятор может оптимизировать все -zero локальная структура (и, возможно, не выделяет ее вообще в стеке, поскольку она будет вписываться в регистры).

+1

Если вы посмотрите на стандартный динамически выделенный объект == с динамической продолжительностью хранения, «на стеке вызовов» == объект с автоматической продолжительностью хранения. –

+0

Спасибо за ваш ответ, этот вопрос связан не столько с управлением памятью, сколько с функциональной разницей двух методов инициализации. – Nit

3
struct foo bar = {0}; 

Это определяет объект типа struct foo имени bar, и инициализирует его к нулю.

«Нуль» определяется рекурсивно. Все целые субобъекты инициализируются на 0, все подобъекты с плавающей запятой на 0.0 и все указатели на NULL.

struct foo * bar2 = calloc(1, sizeof(struct foo)); 

ИМХО это лучше (но то же самое), как написано:

struct foo *bar2 = calloc(1, sizeof *bar2); 

К не повторяя имя типа, мы избегаем риск несоответствия при изменении кода в дальнейшем.

Это динамически выделяет объект типа struct foo (в куче), инициализирует этот объект для всех бит-ноль и инициализирует bar2, чтобы указать на него.

calloc может не выделять память. Если это так, он возвращает нулевой указатель. Вы всегда должны это проверять. (Декларация bar также выделяет память, но если это не удается, это переполнение стека, и нет хорошего способа справиться с этим.)

И весь-бит-ноль не гарантированно быть таким же, как «нулевым ». Для целых типов (включая size_t), это почти гарантировано.Для типов с плавающей запятой и указателями для 0.0 или NULL вполне допустимо иметь внутреннее представление, отличное от всех бит-ноль. Вы вряд ли столкнетесь с этим, и поскольку все члены вашей структуры целы, вам, вероятно, не нужно беспокоиться об этом.

+0

Благодарим вас за ответ, также спасибо за подсказку о синтаксисе размещения. Не могли бы вы уточнить, при каких обстоятельствах ваш последний абзац будет проблемой? Строка, показанная в вопросе, является просто случайным примером. – Nit

+4

@Nit: На практике это вряд ли будет проблемой для любой системы, которую вы, вероятно, будете использовать. Я не думаю, что когда-либо видел C-реализацию, которая использует ничего, кроме all-bits-zero, для нулевых указателей или с плавающей запятой '0.0'. С другой стороны, в стиле, мне нравится писать код, который настолько же портативен, насколько и практичен (но не более того). Очень часто более портативный код оказывается более простым, и это дает вам меньше всего о чем беспокоиться. –

+0

«это почти гарантировано» - Какое исключение? –

0

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

Что касается инициализации, то у компилятора есть возможности для оптимизации с автоматическими переменными (например, если не очистить, если это необходимо); это невозможно с вызовом calloc.

Если вам нравится стиль calloc, у вас также есть возможность выполнить memset в автоматической переменной.

memset(&bar, 0, sizeof bar); 

ОБНОВЛЕНИЕ: выделение автоматических переменных выполняется квазикомандой во время компиляции.

+1

-1 Во время компиляции не происходит ни выделения, ни инициализации автоматических переменных ... как это могло быть? «это невозможно с вызовом calloc» - да, это так, поскольку calloc является стандартной функцией, и ее поведение задается стандартом, поэтому компиляторы могут рассчитывать на него. –

+0

My downvote является искренним и уместным, и мое объяснение для него действительно. То, что вы не приветствуете, не имеет значения, кроме как быть против духа SO. «если строго говоря, компилятор не выделяет пространство памяти» - это факт **, что компилятор не выделяет пространство памяти, он только * генерирует код *, который выделяет это пространство. То же самое для инициализации. Называть это «строго говоря» означает предположить, что говорить «что-либо» происходит во время выполнения, «строго говоря», потому что компилятор сгенерировал код во время компиляции. –

1

(Этот ответ фокусируется на различиях в инициализации, в случае struct содержащих только целочисленных типов)

Обе формы установить a и b в 0. Это связано с тем, что Стандарт определяет, что для каждого целочисленного типа все бит-ноль должен представлять значение 0.

Если есть прокладка структуры, тогда версия calloc устанавливает это, но нулевая инициализация может и не быть. Например:

struct foo a = { 0 }, b = { 0 }; 
struct foo c, d; memset(&c, 0, sizeof c); memset(&d, 0, sizeof d); 

if (memcmp(&a, &b, sizeof a)) 
    printf("This line may appear.\n"); 

if (memcmp(&c, &d, sizeof c)) 
    printf("This line must not appear.\n"); 

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

Программист не хотел сравнивать элементы структуры индивидуально, поскольку это слишком большой размер кода, поэтому вместо этого он будет копировать структуры с использованием memcpy, инициализировать их с помощью memset; чтобы сохранить возможность использовать memcmp для проверки равенства.


В современном программировании я настоятельно рекомендую не делать этого; и всегда использовать форму инициализации { 0 }. Другое преимущество последнего заключается в том, что нет возможности ошибиться с аргументом размера и случайно установить слишком много памяти или слишком мало памяти.

Смежные вопросы