memset () или инициализация значения для обнуления структуры?

46

В программировании API Win32 типично использовать C struct с несколькими полями. Обычно только пара из них имеет значимые ценности, и все остальные должны быть обнулены. Это может быть достигнуто одним из двух способов:

STRUCT theStruct;
memset( &theStruct, 0, sizeof( STRUCT ) );

или

STRUCT theStruct = {};

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

Есть ли у него недостатки по сравнению с первым вариантом? Какой вариант использовать и почему?

Теги:
struct
visual-c++
initialization

8 ответов

70
Лучший ответ

Эти две конструкции очень разные по своему значению. Первая использует функцию memset, которая предназначена для установки буфера памяти на определенное значение. Второй - для инициализации объекта. Позвольте мне объяснить это с помощью кода:

Предположим, что у вас есть структура, в которой есть только члены типов POD

struct POD_OnlyStruct
{
    int a;
    char b;
};

POD_OnlyStruct t = {};  // OK

POD_OnlyStruct t;
memset(&t, 0, sizeof t);  // OK as well

В этом случае запись POD_OnlyStruct t = {} или POD_OnlyStruct t; memset(&t, 0, sizeof t) не имеет большого значения, так как единственное различие, которое мы имеем здесь, это байты выравнивания, устанавливаемые на нулевое значение в случае использования memset. Поскольку у вас нет доступа к этим байтам, нет никакой разницы.

С другой стороны, поскольку вы отметили свой вопрос как С++, попробуйте другой пример, с типами участников, отличными от POD:

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

TestStruct t = {};  // OK

{
    TestStruct t1;
    memset(&t1, 0, sizeof t1);  // ruins member 'b' of our struct
}  // Application crashes here

В этом случае использование выражения типа TestStruct t = {} является хорошим, и использование memset на нем приведет к сбою. Здесь, что происходит, если вы используете memset - создается объект типа TestStruct, создавая таким образом объект типа std::string, поскольку он является членом нашей структуры. Затем memset задает память, в которой объект b находился на определенном значении, например, ноль. Теперь, как только наш объект TestStruct выходит из сферы действия, он будет уничтожен, и когда очередь придет к нему, член std::string b вы увидите сбой, так как все внутренние структуры этого объекта были разрушены memset.

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

Мое голосование - используйте memset для объектов, только если это необходимо, и используйте инициализацию по умолчанию x = {} во всех остальных случаях.

  • 0
    Привет, Близость! У меня есть структура, в которой есть несколько членов, и я попробовал первый вариант установки memset: "struct stVar = {}". Но я получаю предупреждение "-Wmissing-field-initializers". Это проблема?
  • 1
    В данном случае под POD вы подразумеваете фактически тривиально конструируемый объект (т.е. объект без предоставленного пользователем c-tor)? Я не думаю, что это должно быть ограничено POD.
Показать ещё 1 комментарий
27

В зависимости от членов структуры эти два варианта не обязательно эквивалентны. memset установит структуру на все биты-ноль, тогда как инициализация значений будет инициализировать все члены до нулевого значения. Стандарт C гарантирует, что они будут одинаковыми только для интегральных типов, а не для значений с плавающей запятой или указателей.

Кроме того, некоторые API требуют, чтобы структура действительно была установлена ​​на все биты-ноль. Например, API-интерфейс сокетов Berkeley использует структуры полиморфно, и там важно действительно установить всю структуру в нуль, а не только значения, которые очевидны. В документации API должно быть указано, действительно ли структура должна быть полностью-бит-ноль, но она может быть недостаточной.

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

  • 0
    из любопытства, на какой платформе поплавок со всеми битами в нули не является положительным нулем?
  • 3
    Несколько старых процессоров до IEEE-754 имели странные плавающие нули. Математика, не относящаяся к 754, может еще вернуться, вы никогда не знаете, поэтому лучше не писать эти ошибки.
Показать ещё 4 комментария
10

Если ваша структура содержит такие вещи, как:

int a;
char b;
int c;

Затем байты заполнения будут вставлены между "b" и "c". memset() будет нулевым, другой путь не будет, поэтому будет 3 байта мусора (если ваши ints 32 бита). Если вы намерены использовать вашу структуру для чтения/записи из файла, это может быть важно.

  • 0
    Это не похоже на правду. Из CppReference: «Если T является типом класса, не являющимся объединением, все базовые классы и нестатические члены-данные инициализируются нулями, а все заполнение инициализируется нулевыми битами. Конструкторы, если они есть, игнорируются». en.cppreference.com/w/cpp/language/zero_initialization
6

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

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

5

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

  • 0
    while doing a memset would certainly not - не совсем верно. На самом деле, на x86 и x64 memset, установив float / double на ноль, установит его на ноль. Конечно, это не соответствует стандарту C / C ++, но работает на самых популярных платформах.
  • 2
    sbk: пока ... кто знает, какую реализацию с плавающей запятой они могут начать использовать. IEEE 754 не определен для компилятора. Так что даже если это может сработать сейчас, это просто удача для вас, но может вызвать проблемы позже.
3

В некоторых компиляторах STRUCT theStruct = {}; переводится в memset( &theStruct, 0, sizeof( STRUCT ) ); в исполняемый файл. Некоторые функции C уже связаны с настройкой среды выполнения, поэтому у компилятора эти функции библиотеки, такие как memset/memcpy, доступны для использования.

  • 1
    Это на самом деле сильно меня задело в последнее время. Я работал над пользовательским фрагментом кода сжатия и инициализировал некоторые большие структуры во время объявления, используя struct something foo = { x, y, z } и cachegrind показали, что 70% «работы» моей программы memset в memset потому что структуры были обнуляется при КАЖДОМ вызове функции.
2

Инициализация значения, поскольку это можно сделать во время компиляции.
Также он правильно 0 инициализирует все типы POD.

Функция memset() выполняется во время выполнения.
Также использование memset() является подозрительным, если структура не POD.
Не правильно инициализирует (до нуля) типы non int.

  • 3
    Значения не инициализируются во время компиляции. Компилятор генерирует код запуска, который инициализирует все глобальные переменные во время запуска программы, то есть во время выполнения. Для переменных стека инициализация выполняется при входе в функцию - снова во время выполнения.
  • 0
    @qrdl, зависит от компилятора и цели. Для кода, поддерживающего ПЗУ, значения иногда устанавливаются во время компиляции.
Показать ещё 2 комментария
-1

Если есть много элементов указателя, и вы, вероятно, добавите больше в будущем, это может помочь использовать memset. В сочетании с соответствующими вызовами assert(struct->member) вы можете избежать случайных сбоев, пытаясь почтить плохой указатель, который вы забыли инициализировать. Но если вы не так забываетесь, как я, то инициализация членов, вероятно, самая лучшая!

Однако, если ваша структура используется как часть общедоступного API, вы должны получить код клиента для использования memset в качестве требования. Это помогает с будущей проверкой, потому что вы можете добавлять новых членов, а клиентский код автоматически отменяет их в вызове memset, вместо того, чтобы оставлять их в (возможно опасном) неинициализированном состоянии. Это то, что вы делаете при работе с структурами сокетов, например.

  • 0
    Как это помогает в будущем? Если вы предполагаете, что клиентский код не перекомпилирован, он в итоге memset с неправильным размером структуры. Если клиентский код перекомпилирован, ему потребуется доступ к обновленному заголовочному файлу с определением структуры для инициализации memset или значения. (Однако клиент и библиотека должны иметь согласованное представление о том, как представлен нулевой указатель, поэтому, если API рекомендует memset , он должен проверять все нули, а не NULL)
  • 0
    Кроме того, если структура является частью общедоступного API, то, возможно, следует рассмотреть непрозрачную структуру с функцией инициализации.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню