在Win32 API编程中,通常使用带有多个字段的C结构体。其中只有其中几个字段具有有意义的值,而其他所有字段都必须被清零。这可以通过以下两种方式之一实现:
STRUCT theStruct;
memset( &theStruct, 0, sizeof( STRUCT ) );
或者STRUCT theStruct = {};
第二种变体看起来更简洁 - 它只有一行代码,没有可能被输入错误的参数,并导致错误的情况。
与第一种变体相比,它有什么缺点?应该使用哪个变体以及为什么?
在Win32 API编程中,通常使用带有多个字段的C结构体。其中只有其中几个字段具有有意义的值,而其他所有字段都必须被清零。这可以通过以下两种方式之一实现:
STRUCT theStruct;
memset( &theStruct, 0, sizeof( STRUCT ) );
或者STRUCT theStruct = {};
第二种变体看起来更简洁 - 它只有一行代码,没有可能被输入错误的参数,并导致错误的情况。
与第一种变体相比,它有什么缺点?应该使用哪个变体以及为什么?
这两个构造在含义上非常不同。第一个使用了memset
函数,该函数旨在将内存缓冲区设置为特定值。第二个则用于初始化对象。让我举个例子:
假设您有一个只包含POD类型成员的结构体("Plain Old Data" - 参见C++中的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
时唯一的区别是将对齐字节设置为零值。由于通常你无法访问这些字节,所以对你来说没有区别。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
,并在所有其他情况下使用默认初始化x = {}
。
如果T是(可能带有cv限定符的)非联合类类型,则每个非静态数据成员和每个基类子对象都将进行零初始化并且填充将被初始化为零位;
- Clément根据结构成员的不同,这两种变体不一定相等。 memset
会将结构设置为所有位为零,而值初始化将把所有成员初始化为零值。 C标准仅保证这对于整数类型相同,对于浮点值或指针则不一定相同。
此外,一些API要求将结构实际设置为所有位为零。例如,伯克利套接字API在多态使用结构时非常重要,因此确实需要将整个结构设置为零,而不仅仅是显然的值。 API文档应说明是否真正需要将结构设置为所有位为零,但可能存在缺陷。
但是,如果没有这两种情况或类似情况适用,则由您决定。当定义结构时,我更喜欢值初始化,因为这样可以更清晰地传达意图。当然,如果需要将现有结构归零,则memset
是唯一的选择(除手动将每个成员初始化为零之外,但通常不会执行此操作,特别是对于大型结构)。
int a;
char b;
int c;
那么在b
和c
之间将会插入填充字节。使用memset
函数会把这些字节清零,而其他方法则不会,因此会有3个垃圾字节(如果您的整数是32位)。如果您打算使用结构体从文件中读取/写入数据,则这可能很重要。
memset
来将其清零。虽然较少见,但我猜第二种方法还有一个好处,就是将浮点数初始化为零,而使用 memset
则不一定能做到这一点。
值初始化是首选的,因为它可以在编译时完成。
此外,它正确地将所有POD类型初始化为0。
memset
在运行时完成。
如果结构体不是POD,则使用memset
是可疑的。
不会正确地初始化(为零)非int类型。
STRUCT theStruct = {};
可以被翻译成 memset( &theStruct, 0, sizeof( STRUCT ) );
在可执行文件中。一些 C 函数已经被链接到运行时环境中,因此编译器可以使用这些库函数,例如 memset/memcpy。struct something foo = { x,y,z }
来初始化一些大型结构体。然而 cachegrind 显示,我的程序中 70% 的“工作”都花在了 memset
上,因为每次函数调用时结构体都会被清零。 - Jody Bruchon assert(struct->member)
调用,您可以避免因尝试引用您忘记初始化的坏指针而导致的随机崩溃。但是,如果您不像我这样健忘,那么成员初始化可能是最好的选择!
然而,如果您的结构体作为公共API的一部分被使用,您应该要求客户端代码使用memset。这有助于未来的保护,因为您可以添加新的成员,并且客户端代码将自动在memset调用中将其归零,而不是将其保留在(可能危险的)未初始化状态中。例如,在使用套接字结构时就是这样做的。memset
。如果客户端代码被重新编译,则需要访问包含memset
或值初始化的结构定义的更新标头文件。(但是,客户端和库确实需要具有关于空指针表示方式的一致概念,因此,如果API建议使用memset
,则应该针对所有位零进行检查,而不是针对NULL。) - jamesdlin