繁体   English   中英

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

[英]memset() or value initialization to zero out a struct?

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

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) 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

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

所以,现实情况是,那些东西有很大的不同,虽然有时需要memset整体结构,在某些情况下零,它总是重要的是要确保你知道你在做什么,而不是犯了一个错误在我们的第二个例子。

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

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

此外,某些 API 要求将结构真正设置为所有位为零。 例如,伯克利套接字 API 以多态方式使用结构,重要的是将整个结构真正设置为零,而不仅仅是明显的值。 API 文档应该说明结构是否真的需要全部为零,但它可能有缺陷。

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

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

int a;
char b;
int c;

然后将在“b”和“c”之间插入填充字节。 memset() 会将它们归零,反之则不会,因此将有 3 个字节的垃圾(如果您的整数是 32 位)。 如果您打算使用结构从文件中读/写,这可能很重要。

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

不过,您可能会依赖memset在使用后将结构归零。

并不是说它很常见,但我想第二种方法也有将浮点数初始化为零的好处。 虽然做 memset 肯定不会

值的初始化因为它可以在编译时完成。
它也正确 0 初始化所有 POD 类型。

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

在某些编译器中, STRUCT theStruct = {}; 将转换为memset( &theStruct, 0, sizeof( STRUCT ) ); 在可执行文件中。 一些 C 函数已经链接到执行运行时设置,因此编译器可以使用这些库函数,如 memset/memcpy。

如果有很多指针成员并且您将来可能会添加更多成员,则使用 memset 会有所帮助。 结合适当的assert(struct->member)调用,您可以避免因尝试遵从您忘记初始化的坏指针而导致的随机崩溃。 但如果你不像我那么健忘,那么成员初始化可能是最好的!

但是,如果您的结构被用作公共 API 的一部分,您应该让客户端代码使用 memset 作为要求。 这有助于未来验证,因为您可以添加新成员,并且客户端代码将在 memset 调用中自动将它们清空,而不是将它们留在(可能危险的)未初始化状态。 例如,这就是您在使用套接字结构时所做的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM