2010-05-14 3 views
17

Зачастую инициализация структур данных требует, чтобы все элементы были равны нулю. Даже при программировании на C++ может потребоваться интерфейс с внешним API, для которого это так.Инициализация объекта ко всем нулям

Есть ли практическая разница между:

some_struct s; 
memset(&s, 0, sizeof(s)); 

и просто

some_struct s = { 0 }; 

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

Для меня, как в основном программиста на С ++, который не использует memset много, я никогда не определенная подписи функции, поэтому я считаю, что второй пример проще использовать в дополнение к тому, чтобы быть менее типичным, более компактным и, возможно, даже более очевидным, так как он говорит «этот объект инициализируется до нуля "прямо в объявлении, а не ждет следующей строки кода и видит," oh, этот объект инициализируется нолем ".

При создании классов и структур на C++ я обычно использую списки инициализации; Мне любопытно думать о мыслях о двух инициализациях «C style» выше, а не о сравнении с тем, что доступно на C++, поскольку я подозреваю, что многие из нас взаимодействуют с библиотеками C, даже если мы сами кодируем в основном на C++.

Редактировать: Нейл Баттерворт поставил this question, что я считаю интересным следствием этого вопроса.

+1

В C++ инициализация нуля не обеспечивает большого функционального использования, поскольку конструктор гарантирует, что тип готов к работе. – Puppy

+0

@DeadMG: Что делает конструктор типа POD? –

+0

@ dash-tom-bang: Он абсолютно ничего не делает. – AnT

ответ

22

memset Практически никогда не будет правильным способом. И да, есть практическая разница (см. Ниже).

В C не ++ все может быть инициализировано с буквальным 0 (объектами типов перечислений не может быть), поэтому в C++ общего идиом

some_struct s = {}; 

в то время как в C идиом является

some_struct s = { 0 }; 

Обратите внимание, что в C = { 0 } - это то, что можно назвать универсальным инициализатором нуля. Он может быть использован с объектами практически любого типа, так как {} -enclosed инициализаторы разрешены со скалярными объектами, а

int x = { 0 }; /* legal in C (and in C++) */ 

что делает = { 0 } полезные в универсального типа-независимый код C (макросы типа-независимый, например).

Недостатком инициализатора = { 0 } в C89/90 и C++ является то, что он может использоваться только как часть декларации. (C99 исправил эту проблему, представив составные литералы. Аналогичная функциональность подходит и для C++.) По этой причине многие программисты могут использовать memset, чтобы что-то вывести из середины кода C89/90 или C++. Тем не менее, я бы сказал, что правильный способ сделать это еще не memset, а с чем-то вроде

some_struct s; 
... 
{ 
    const some_struct ZERO = { 0 }; 
    s = ZERO; 
} 
... 

т.е. введения «фиктивный» блока в середине коды, даже если он не может выглядеть слишком довольно на первый взгляд. Конечно, в C++ нет необходимости вводить блок.

Что касается практической разницы ... Возможно, вы слышали, что некоторые люди говорят, что memset будет давать те же результаты на практике, так как на практике физический все-нулевой бит-шаблон является тем, что используется для представления нулевых значений для всех типов. Однако это, как правило, неверно. Непосредственный пример того, что бы продемонстрировать разницу в типичной реализации C++ является указатель на данных балку типа

struct S; 
... 

int S::*p = { 0 }; 
assert(p == NULL); // this assertion is guaranteed to hold 

memset(&p, 0, sizeof p); 
assert(p == NULL); // this assertion will normally fail 

Это происходит потому, что типичная реализация обычно использует весь-один битовый шаблон (0xFFFF...) представлять нулевой указатель этого типа. Приведенный выше пример демонстрирует реальную практическую разницу между обнулением memset и обычным инициализатором = { 0 }.

+1

@AndreyT: '" Конечно, в C++ нет необходимости вводить блок ", почему? Кроме того, почему вы хотите ввести блок в C? – Lazer

+1

@eSKay: поскольку C++ позволяет добавлять объявления в середине кода. Нет необходимости в блоке. C99 также допускает это, но, как я уже говорил выше, на C99 у вас есть лучший вариант: сложные литералы. Единственный случай, который остался открытым, - C89/90. В C89/90 вы не можете просто объявить переменную в середине кода. Вам нужен блок. Вот почему я хочу ввести блок в C (подразумевая C89/90). – AnT

+0

@AndreyT: спасибо! никогда не знал, что «в C89/90 вы не можете просто объявить переменную в середине кода». Но я протестировал [этот код] (http://codepad.org/nx3vzW1o) с помощью 'gcc -std = c89 check89.c', и он компилируется и работает отлично! – Lazer

15

some_struct s = { 0 }; гарантированно работает; memset опирается на детали реализации и лучше избегать.

+4

Если это о C++, то нет, это будет * not * работать, когда первый член 'some_struct' является объектом перечисления. Это одна из причин, по которой инициализаторы '= {}' были введены в C++. – AnT

6

Если структура содержит указатели, значение всех битов нулю, производимых memset не может означать то же самое, как и присвоение 0 к нему в C (или C++) кода, т.е. NULL указателя.

(.. Это также может быть случай с floats и doubles, но я никогда не сталкивался, однако, я не думаю, что стандарты гарантируют их, чтобы стать нулем с memset либо)

Edit: С более прагматичной точки зрения, я бы сказал, что не следует использовать memset, когда это возможно, чтобы избежать, поскольку это дополнительный вызов функции, дольше писать и, по моему мнению, менее ясны в намерении, чем = { 0 }.

+2

Стандарты C и C++ не требуют, чтобы все биты нуль превращались в 0.0 как число с плавающей запятой, но стандарты IEEE. Существуют машины, которые не соответствуют стандартам IEEE, но все из них, о которых я знаю, все равно преобразуют все биты в 0. –

+1

Вопрос, заданный для * практических * отличий. Практически говоря, нулевые указатели всегда имеют все биты-нуль, а также нулевые значения типов с плавающей точкой. –

+0

На платформах, с которыми я знаком, все используют биты-ноль для обозначения NULL, 0 и 0.0, но ваша точка хорошо взята. –

5

В зависимости от оптимизации компилятора может быть некоторый порог, выше которого memset работает быстрее, но обычно это будет значительно выше обычного размера переменных на основе стека. Использование memset на объекте C++ с виртуальной таблицей, конечно, плохо.

+0

Использование 'memset' на любом не-POD плохо. Кроме того, если 'memset' быстрее инициализирует что-то, я не понимаю, почему компилятор тогда не оптимизировал' = {} 'на такую ​​вещь. – GManNickG

0

bzero функция - другой вариант.

#include <strings.h> 
void bzero(void *s, size_t n); 
+2

'bzero' не является стандартным и может быть недоступен на всех платформах. От Harbison & Steele: «Более ограниченная функция« bzero »... найдена в некоторых реализациях UNIX». Он не указывает, что функция доступна во всех реализациях. –

3

Единственное практическое отличие заключается в том, что синтаксис ={0}; немного понятнее о высказывании «инициализировать это быть пустым» (по крайней мере, кажется мне яснее).

Теоретически существует несколько ситуаций, в которых memset может потерпеть неудачу, но, насколько я знаю, они действительно такие: теоретические. OTOH, учитывая, что он уступает теоретическому и практическим точкам зрения, мне трудно понять, почему кто-то захочет использовать memset для этой задачи.

+0

'memset' не работает' = {} 'в реальных условиях. (Я не знаю, что такое имена, но я знаю, что они существуют. И они современные.) – GManNickG

+0

@GMan: Мне известно о нескольких средах, в которых обычное представление нулевого указателя не имеет всех битов нуля - но, по крайней мере, в тех, о которых я знаю, all-bits-zero * does * также создает нулевой указатель. Конечно, есть машины, которые я не использовал. Как я уже сказал, хотя я не могу указать на это, я не могу представить себе веские основания использовать «memset». –

+2

Вы не слишком внимательно смотрели. Практически все популярные реализации C++ (например, G ++) используют ** битовый шаблон ** all-ones ** для нулевых указателей типов * pointer-to-data-member * в C++. Подобно 'int S :: * p' для некоторого типа класса' S'. Вы не получите нулевой указатель, выполнив «memset (& p, 0, sizeof p)» с таким указателем. – AnT

2

Я никогда не понимал таинственной доброты, устанавливающей все на ноль, которая, даже если она определена, кажется маловероятной. Поскольку это помечено как C++, правильным решением для инициализации является создание структуры или класса construtor.

+1

Кажется, что это крутой скандал, когда вы имеете дело с сторонними библиотеками, написанными на C, к которым у вас нет исходного кода. –

+0

Это менее произвольно, если ваша структура содержит указатели, принадлежащие структуре, так как тогда функция очистки может безоговорочно вызывать 'free' (или' delete'). – jamesdlin

2

Я думаю, что инициализация говорит намного яснее, что вы на самом деле делаете. Вы инициализируете структуру. Когда новый стандарт выходит из этого способа инициализации, он будет еще более использоваться (инициализация контейнеров с {} - это что-то, на что можно рассчитывать). Способ memset немного более подвержен ошибкам, и не сообщает, что ясно, что вы делаете. Это может не учитывать многое при программировании в одиночку, но это очень важно при работе в команде.

Для некоторых людей, работающих с C++, memset, malloc & co. являются довольно эзотерическими существами. Я встречался с несколькими.

+0

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

2

Лучший метод для очистки конструкций является установка каждого поля в отдельности:

struct MyStruct 
{ 
    std::string name; 
    int age; 
    double checking_account_balance; 
    void clear(void) 
    { 
    name.erase(); 
    age = 0; 
    checking_account_balance = 0.0; 
    } 
}; 

В приведенном выше примере, метод clear определен для установки всех членов в известное состояние или значение. Методы memset и std::fill могут не работать из-за std::string и double. Более надежная программа очищает каждое поле отдельно.

Я предпочитаю иметь более надежную программу, чем тратить меньше времени на ввод.

3

Hopefully it is understood that this is only currently available for POD structures; you'd get a compiler error if there was a C++ std::string in that structure.

Нет, не будет. Если вы используете memset на таких, в лучшем случае вы просто потерпите крах, и в худшем случае вы получите какую-то тарабарщину. Способ = { } может быть отлично использован для не-POD-структур, если они являются агрегатами. Способ = { } - лучший способ взять на C++. Пожалуйста, обратите внимание, что нет никаких причин, в C++, чтобы положить, что 0 в нем, а также не рекомендуется, так как это резко сокращает случаи, в которых она может быть использована

struct A { 
    std::string a; 
    int b; 
}; 

int main() { 
    A a = { 0 }; 
    A a = { }; 
} 

Первый не будет делать то, что вы хотите: Это попытается создать std::string из C-строки с указанием нулевого указателя на его конструктор. Второй, однако, делает то, что вы хотите: он создает пустую строку.

+0

Ahh достаточно честный; Я удалю это редактирование. Если учесть, что моя память о «неагрегатах не может быть инициализирована списком инициализатора», ошибки облака мои мысли ... Конечно, любой объект, который имеет один аргумент (не явный?) Ctor, будет иметь такое поведение. В случае std :: string, счастливо выполняем строковые операции с NULL. –

0

В C Я предпочитаю использовать {0,} эквивалентную memset(). Однако НКУ предупреждает об этом использовании :(Подробности здесь: http://www.pixelbeat.org/programming/gcc/auto_init.html

В C++ они обычно эквивалентны, но как всегда с C++ есть частные случаи, которые следует учитывать (отмеченное в других ответах)

4

I. нашел хорошее решение быть:

template<typename T> void my_zero(T& e) { 
    static T dummy_zero_object; 
    e = dummy_zero_object; 
} 

my_zero(s); 

Это делает правильную вещь не только для основных типов и типов, определяемых пользователем, но и нулевой инициализирует типы, для которых конструктор по умолчанию определяется, но не инициализирует все члена переменные --- esp классические классы, содержащие нетривиальные члены union.

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