memset()或值初始化将结构清零?

sha*_*oth 63 c c++ struct initialization visual-c++

在Win32 API编程中,通常使用struct具有多个字段的C s.通常只有其中几个具有有意义的值,而其他所有值都必须归零.这可以通过以下两种方式之一实现:

STRUCT theStruct;
memset( &theStruct, 0, sizeof( STRUCT ) );
Run Code Online (Sandbox Code Playgroud)

要么

STRUCT theStruct = {};
Run Code Online (Sandbox Code Playgroud)

第二个变体看起来更干净 - 它是一个单行,它没有任何可能输错的参数并导致错误被种植.

与第一个变体相比,它有任何缺点吗?使用哪种变体?为什么?

Dmi*_*try 86

这两个构造的含义非常不同.第一个使用一个memset函数,用于将内存缓冲区设置为某个值.第二个初始化对象.让我用一些代码解释一下:

让我们假设您的结构只包含POD类型的成员

struct POD_OnlyStruct
{
    int a;
    char b;
};

POD_OnlyStruct t = {};  // OK

POD_OnlyStruct t;
memset(&t, 0, sizeof t);  // OK as well
Run Code Online (Sandbox Code Playgroud)

在这种情况下写一个POD_OnlyStruct t = {}POD_OnlyStruct t; memset(&t, 0, sizeof t)没有太大的区别,因为我们这里唯一的区别是在使用的情况下将对齐字节设置为零值memset.由于您无法正常访问这些字节,因此对您没有任何区别.

另一方面,由于您已将问题标记为C++,让我们尝试另一个示例,其成员类型与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
Run Code Online (Sandbox Code Playgroud)

在这种情况下,使用类似的表达式TestStruct t = {},并使用memset它将导致崩溃.如果您使用,会发生以下情况memset- TestStruct创建类型的对象,从而创建类型的对象std::string,因为它是我们结构的成员.接下来,memset将对象所在的内存b设置为特定值,即零.现在,一旦我们的TestStruct对象超出范围,它就会被破坏,当轮到它的成员时,std::string b你会看到一个崩溃,因为所有对象的内部结构都被破坏了memset.

所以,实际情况是,那些事情是非常不同的,尽管memset在某些情况下你有时需要将整个结构归零,但确保你理解你正在做什么并且不要像我们的第二个那样犯错,这一点很重要.例.

我的投票 - 在需要时才使用memset对象,并在所有其他情况下使用默认初始化.x = {}

  • 我认为这个答案已经过时了。在 C++11 中,填充位保证为零初始化:“如果 T 是(可能是 cv 限定的)非联合类类型,则每个非静态数据成员和每个基类子对象都是零初始化的,并且填充初始化为零位;` (3认同)
  • 在这种情况下,*POD* 你的意思是实际上一个*简单可构造的*对象(即没有用户提供的 c-tor 的对象)?我不认为它应该被限制在 POD 上。 (2认同)

Jaa*_*koK 29

根据结构成员,这两种变体不一定相同.memset将结构设置为all-bits-zero,而值初始化将所有成员初始化为零值.C标准保证这些仅对于整数类型是相同的,而不是浮点值或指针.

此外,某些API要求结构确实设置为全位为零.例如,Berkeley套接字API以多态方式使用结构,并且真正将整个结构设置为零非常重要,而不仅仅是显而易见的值.API文档应该说明结构是否真的需要全位为零,但它可能是不足的.

但是,如果这些或类似情况都不适用,那么这取决于你.在定义结构时,我会更喜欢值初始化,因为它更清楚地传达了意图.当然,如果您需要将现有结构归零,memset则是唯一的选择(除了手动将每个成员初始化为零,但通常不会这样做,特别是对于大型结构).

  • 几个旧的IEEE-754之前的CPU有奇怪的浮动零.非754数学可能会回来,你永远不知道,所以最好不要写那些错误. (3认同)
  • 我现在猜的并不是很多,因为IEEE如此常见,但它们曾经更为普遍.我理解软件FP实现是典型的例子,其中零不是全位零.所以你可能不会遇到麻烦,但是,C仍然没有强制要求IEEE,所以除非零初始化是一个瓶颈,否则"更安全"的方式并不会花费任何成本. (2认同)

peu*_*feu 9

如果您的结构包含以下内容:

int a;
char b;
int c;
Run Code Online (Sandbox Code Playgroud)

然后将在"b"和"c"之间插入填充字节.memset()将为零,其他方式不会,因此将有3个字节的垃圾(如果你的整数是32位).如果您打算使用结构从文件读取/写入,这可能很重要.

  • 这似乎不是真的。来自 CppReference:“如果 T 是非联合类类型,则所有基类和非静态数据成员都初始化为零,所有填充都初始化为零位。构造函数(如果有)将被忽略。” https://en.cppreference.com/w/cpp/language/zero_initialization (2认同)

Gre*_*osz 7

我会使用值初始化,因为它看起来很干净,并且不像你提到的那样容易出错.这样做我没有看到任何缺点.

memset在使用它之后,您可能会依赖于将结构清零.


Mar*_*ork 6

值初始化是首选,因为它可以在编译时完成。
它还正确地 0 初始化所有 POD 类型。

memset是在运行时完成的。如果该结构不是 POD,那么
使用也是值得怀疑的。 未正确初始化(为零)非 int 类型。memset

  • 值不会在编译时初始化。编译器生成启动代码,在程序启动期间初始化所有全局变量,从而在运行时初始化。对于堆栈变量,初始化是在函数入口处执行的 - 再次在运行时执行。 (4认同)
  • @qrdl:让我重新表述一下。值初始化可能允许(在某些情况下)编译器在编译时(而不是运行时)进行初始化。因此 POD 只能在编译时初始化全局变量。 (3认同)

Toa*_*oad 5

不是这很常见,但我猜第二种方式也有将初始化浮点数归零的好处.虽然做一个memset肯定不会

  • sbk:现在......谁知道他们可能会开始使用什么浮点实现.IEEE 754未针对编译器定义.所以,即使它现在可以运作,它对您来说也是幸运的,但可以在以后提出问题. (2认同)