Подтвердить что ты не робот

Инициализация объекта ко всем нулям

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

Существует ли какая-либо практическая разница между:

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

и просто

some_struct s = { 0 };

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

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

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

Изменить: Нейл Баттерворт поставил этот вопрос в последующем, что я считаю интересным следствием этого вопроса.

4b9b3361

Ответ 1

memset практически не подходит для этого. И да, есть практическая разница (см. Ниже).

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

some_struct s = {};

а в C идиома

some_struct s = { 0 };

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

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

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

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

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

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

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

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

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

Ответ 2

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

Ответ 3

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

(Возможно, это также относится к floats и doubles, но я никогда не встречался. Однако я не думаю, что стандарты гарантируют, что они станут равными нулю с помощью memset.)

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

Ответ 4

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

Ответ 5

Я нашел хорошее решение:

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

my_zero(s);

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

Ответ 6

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

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

Ответ 7

Мы надеемся, что это доступно только для структур POD; вы получите ошибку компилятора, если в этой структуре был С++ std::string.

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

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

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

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

Ответ 8

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

Ответ 9

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

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

Ответ 10

Лучший способ очистки структур - установить каждое поле отдельно:

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. Более надежная программа очищает каждое поле отдельно.

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

Ответ 11

Функция bzero - это еще одна опция.

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

Ответ 12

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

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