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


76

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

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

要么

STRUCT theStruct = {};

第二个变体看起来更干净-它是单线的,它没有任何可能被错误键入并导致植入错误的参数。

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


本“如何对后一个问题的答案[1]”似乎更有用且更容易。[1]:stackoverflow.com/questions/4625212/class-initialization-list/...
TheMatto

Answers:


94

这两个结构在含义上有很大不同。第一个使用一个memset函数,该函数旨在将内存缓冲区设置为某个值。第二个初始化一个对象。让我用一些代码来解释一下:

假设您有一个仅包含POD类型成员的结构(“普通旧数据”-请参阅C ++中的POD类型是什么?

struct POD_OnlyStruct
{
    int a;
    char b;
};

POD_OnlyStruct t = {};  // OK

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

在这种情况下,写aPOD_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

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

因此,现实是,这些事情非常不同,尽管有时memset在某些情况下您需要将整个结构归零,但确保您了解自己在做什么,而不是像我们在第二篇文章中那样犯错,始终很重要例。

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


嗨,Dimity!我有一个包含一些成员的结构,并且尝试了记忆设置的第一个选项:“ struct stVar = {}”。但是我收到“ -Wmissing-field-initializers”警告。有问题吗?
MayurK

1
在这种情况下,按POD的意思是实际上是可构造的对象(即没有用户提供的c-tor的对象)吗?我认为它不应该局限于POD。
Al.G.

这不会崩溃:coliru.stacked-crooked.com/a/4b3dbf0b8761bc9b从技术上讲,这是未定义的行为,因为该结构不能轻易分配(因此会出现编译器警告)。但是,我怀疑是否存在任何通用平台,其中零字节是的无效值std::string
凯尔·斯特兰德

我认为这个答案已经过时了。在C ++ 11,填充比特保证是零初始化:if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
克莱门特

29

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

此外,某些API要求将结构真正设置为全零位。例如,Berkeley套接字API多态使用结构,重要的是将整个结构真正设置为零,而不仅仅是将表面上的值设置为零。API文档应说明该结构是否确实需要为全零位,但可能会有所不足。

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


出于好奇,在哪个平台上所有位都为零的浮点数不是正零吗?
格雷戈里·帕科斯

3
几个旧的IEEE-754之前的CPU具有奇怪的浮点零。您还不知道,非754数学可能会重新出现,因此最好不要编写这些bug。
Andrew McGregor,2010年

1
没关系 C标准未指定使用哪种浮点格式。因此,即使它现在适用于IEEE 754,也可能无法在其他浮动实现上使用(将来或过去)
Toad 2010年

3
我猜想,在如今的IEEE如此普遍的情况下,如今已经不多了,但是它们曾经更加流行。我了解软件FP实现是典型示例,其中零不是全零。因此,您可能不会遇到麻烦,但是C仍然没有强制执行IEEE,因此,除非零初始化成为瓶颈,否则“更安全”的方式实际上不会花费任何费用。
JaakkoK,2010年

1
将每个成员初始化为零不会使每个成员都为零,但是您会错过填充字节。因此,内存集是您唯一的选择。
fmuecke 2012年

11

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

int a;
char b;
int c;

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


2
事实并非如此。来自CppReference:“如果T是非联合类类型,则所有基类和非静态数据成员都将初始化为零,并且所有填充都将初始化为零位。如果有构造函数,则将被忽略。” cn.cppreference.com/w/cpp/language/zero_initialization
凯尔·斯特兰德

可能仅适用于C,不适用于C ++。
syockit

7

我将使用值初始化,因为它看起来很干净,并且您提到的错误更少。我认为这样做没有任何缺点。

memset不过,在使用该结构后,您可能需要将其归零。


6

并不是说这很普遍,但是我想第二种方式也具有将浮点数初始化为零的好处。虽然做一个记忆集肯定不会


while doing a memset would certainly not-并非完全正确。实际上,在x86和x64上,将float / double设置为零会将其设置为零。当然,这不在C / C ++标准中,但是可以在最受欢迎的平台上使用。
sbk 2010年

2
sbk:现在……谁知道他们可能会开始使用哪种浮点实现。未为编译器定义IEEE 754。因此,即使它现在可以工作,这对您来说还是很幸运的,但是以后可能会出现问题。
Toad

4

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

memset()在运行时完成。
如果该结构不是POD,也可能会使用memset()。
没有正确初始化(为零)非int类型。


3
值不会在编译时初始化。编译器生成启动代码,该代码在程序启动期间(因此在运行时)初始化所有全局变量。对于堆栈变量,初始化是在函数入口上执行的-再次在运行时。
qrdl 2010年

@qrdl,取决于编译器和目标。对于支持ROM的代码,有时会在编译时设置值。
法肯教授

2
@qrdl:让我重新表述一下。值初始化可以(在某些情况下)允许编译器在编译时(而不是运行时)进行初始化。因此,仅POD全局变量可以在编译时进行初始化。
马丁·约克

@qrdl:在许多平台上,如果“ foo”是静态存储类的Int32_t,则运行时语句“ foo = 0x12345678;” 将生成代码以将0x12345678存储在foo中;该代码可能至少有十个字节长,某些微控制器将需要多达32个字节。声明“ Int32_t foo = 0x12345678;” 在许多平台上会导致变量链接到初始化数据段中,并向初始化列表添加4个字节。在某些系统上,“ Int32_t foo;” 比“ Int32_t foo = 0;”便宜4个字节,后者将foo强制给初始化数据段。
supercat

3

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


2
这最近真的让我很难受。我正在处理一段自定义的压缩代码,并在声明时使用初始化了一些大型结构,struct something foo = { x, y, z }并且cachegrind表明我程序的70%的“工作”都在其中,memset因为在每次函数调用时都将这些结构归零。
乔迪·布鲁雄

-1

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

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


它对未来的发展有何帮助?如果您假设未重新编译客户端代码,则最终memset将以错误的结构大小进行调用。如果客户端代码被重新编译,则需要访问具有结构定义的更新头文件,以使memset值初始化或工作。(客户端和库确实需要对表示空指针的方式有一个一致的概念,因此,如果API建议memset,则应针对全零位进行检查,而不是针对NULL。)
jamesdlin

同样,如果该结构是公共API的一部分,那么也许应该考虑使用带有初始化函数的不透明结构。
jamesdlin
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.