c++ memset()或值初始化来清零结构体?

pw9qyyiw  于 2023-01-06  发布在  其他
关注(0)|答案(8)|浏览(366)

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

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

STRUCT theStruct = {};

第二个变体看起来更干净--它是一行程序,没有任何可能被错误键入并导致植入错误的参数。
与第一个变体相比,它有什么缺点吗?使用哪个变体,为什么?

cigdeys3

cigdeys31#

这两个构造在意义上 * 非常 * 不同。第一个构造使用memset函数,该函数用于 * 将内存缓冲区设置为某个值 *。第二个构造用于 * 初始化对象 *。让我用一些代码来解释它:
让我们假设您有一个结构,它的成员 * 仅为POD类型 *(“Plain Old Data”-请参见What are POD types in C++?

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的情况下,alignment bytes被设置为零值。因为你通常不能访问这些字节,所以对你来说没有区别。
另一方面,既然你已经将问题标记为C++,让我们尝试另一个例子,其中member * 类型不同于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所在的内存设置为某个值,比如0,现在,一旦我们的TestStruct对象超出作用域,它将被销毁,当轮到它的成员std::string b时,您将看到崩溃,因为所有的内部结构都被X1 M12 N1 X破坏了。
所以,现实是,* 这些事情是非常不同的 *,尽管在某些情况下有时需要将整个结构memset为零,但确保您理解自己在做什么始终是重要的,而不是像我们的第二个示例中那样犯错误。
我的投票-只在需要的时候在对象上使用memset,在所有其他情况下使用默认的初始化x = {}

zujrkrfu

zujrkrfu2#

memset将把结构设置为所有位为零,而值初始化将把所有成员初始化为零。C标准保证这些只对整数类型相同,而对浮点值或指针不相同。
另外,一些API要求结构真的设置为全位零。例如,Berkeley套接字API以多态方式使用结构,因此将整个结构设置为零非常重要,而不仅仅是将表面上的值设置为零。API文档应该说明结构是否真的需要全位零,但这可能是有缺陷的。
但是如果这两种方法都不适用,或者类似的情况,那么就由你来决定了。当定义结构时,我更喜欢值初始化,因为这样可以更清楚地表达意图。当然,如果你需要将一个现有的结构置零,memset是唯一的选择(当然,除了手动将每个成员初始化为零,但通常不会这样做,特别是对于大型结构)。

avwztpqn

avwztpqn3#

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

int a;
char b;
int c;

然后在bc之间插入填充字节。memset会将这些字节置零,另一种方法不会,所以会有3个字节的垃圾(如果你的int是32位)。如果你打算用你的结构体读/写一个文件,这可能很重要。

jm81lzqq

jm81lzqq4#

我会使用值初始化,因为它看起来很干净,而且像你提到的那样不容易出错。我看不出这样做有什么缺点。
不过,您可能会依赖memset在使用结构体之后将其清零。

7kqas0il

7kqas0il5#

这并不常见,但我想第二种方法也有将浮点数初始化为零的好处,而memset肯定不会。

u0njafvf

u0njafvf6#

首选值初始化,因为它可以在编译时完成。
此外,其正确0初始化所有POD类型。
memset在运行时完成。
如果结构不是POD,则使用memset也是可疑的。
未正确初始化(为零)非int类型。

kzmpq1sx

kzmpq1sx7#

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

llmtgqce

llmtgqce8#

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

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

相关问题