将对象初始化为零

2021-12-23 00:00:00 c struct c++

通常数据结构的有效初始化是将所有成员设置为零.即使在使用 C++ 编程时,也可能需要与外部 API 接口,这种情况就是如此.

是否有任何实际区别:

some_struct s;memset(&s, 0, sizeof(s));

而且很简单

some_struct s = { 0 };

人们是否发现自己同时使用这两种方法,并有一种方法可以选择哪种方法更适合给定的应用程序?(希望大家理解,这目前仅适用于 POD 结构;如果该结构中有 C++ std::string,您会受到各种破坏.)

就我自己而言,作为一个不经常使用 memset 的 C++ 程序员,我从不确定 函数签名,所以我发现第二个示例不仅更易于使用更少的输入,更紧凑,甚至可能更明显,因为它在声明中说这个对象被初始化为零",而不是等待下一行代码并看到哦,这个对象被初始化为零".

在 C++ 中创建类和结构时,我倾向于使用初始化列表;我很好奇人们对上述两个C 风格"初始化的想法,而不是与 C++ 中可用的比较,因为我怀疑我们中的许多人与 C 库交互,即使我们自己主要用 C++ 编写代码.

Neil Butterworth 在后续中提出了这个问题,我认为这是这个问题的一个有趣的推论.

解决方案

memset 实际上从来都不是正确的方法.是的,存在实际差异(见下文).

在 C++ 中,并非所有东西都可以用文字 0 初始化(枚举类型的对象不能),这就是为什么在 C++ 中常见的习惯用法是

some_struct s = {};

而在 C 中的习语是

some_struct s = { 0 };

请注意,在 C 中,= { 0 } 可以称为通用零初始值设定项.它几乎可以用于任何类型的对象,因为 {} 封闭的初始值设定项也允许用于标量对象

int x = { 0 };/* 在 C(和 C++)中合法 */

这使得 = { 0 } 在与类型无关的泛型 C 代码(例如与类型无关的宏)中很有用.

C89/90 和 C++ 中 = { 0 } 初始值设定项的缺点是它只能用作声明的一部分.(C99 通过引入复合文字解决了这个问题.类似的功能也出现在 C++ 中.)出于这个原因,您可能会看到许多程序员使用 memset 来将某些东西清零在 C89/90 或 C++ 中间的代码.然而,我会说正确的做法仍然是没有 memset 而是使用类似

some_struct s;...{const some_struct 零 = { 0 };s = 零;}...

即通过在代码中间引入一个虚构"块,即使它乍一看可能不太漂亮.当然,在 C++ 中不需要引入块.

至于实际差异...你可能会听到有人说 memset 在实践中会产生相同的结果,因为在实践中物理全零位模式是用来表示的所有类型的零值.然而,这通常不是真的.一个可以证明典型 C++ 实现差异的直接示例是指向数据成员的指针类型

struct S;...int S::*p = { 0 };断言(p == NULL);//这个断言保证成立memset(&p, 0, sizeof p);断言(p == NULL);//这个断言通常会失败

发生这种情况是因为典型的实现通常使用全一位模式 (0xFFFF...) 来表示这种类型的空指针.上面的示例演示了归零 memset 和普通 = { 0 } 初始化程序之间的实际差异.

Oftentimes data structures' valid initialization is to set all members to zero. Even when programming in C++, one may need to interface with an external API for which this is the case.

Is there any practical difference between:

some_struct s;
memset(&s, 0, sizeof(s));

and simply

some_struct s = { 0 };

Do folks find themselves using both, with a method for choosing which is more appropriate for a given application? (Hopefully it is understood that this is only currently applicable to POD structures; you'd get all sorts of havoc if there was a C++ std::string in that structure.)

For myself, as mostly a C++ programmer who doesn't use memset much, I'm never certain of the function signature so I find the second example is just easier to use in addition to being less typing, more compact, and maybe even more obvious since it says "this object is initialized to zero" right in the declaration rather than waiting for the next line of code and seeing, "oh, this object is zero initialized."

When creating classes and structs in C++ I tend to use initialization lists; I'm curious about folks thoughts on the two "C style" initializations above rather than a comparison against what is available in C++ since I suspect many of us interface with C libraries even if we code mostly in C++ ourselves.

Edit: Neil Butterworth posed this question, in followup, that I believe is an interesting corollary to this question.

解决方案

memset is practically never the right way to do it. And yes, there is a practical difference (see below).

In C++ not everything can be initialized with literal 0 (objects of enum types can't be), which is why in C++ the common idiom is

some_struct s = {};

while in C the idiom is

some_struct s = { 0 };

Note, that in C the = { 0 } is what can be called the universal zero initializer. It can be used with objects of virtually any type, since the {}-enclosed initializers are allowed with scalar objects as well

int x = { 0 }; /* legal in C (and in C++) */

which makes the = { 0 } useful in generic type-independent C code (type-independent macros for example).

The drawback of = { 0 } initializer in C89/90 and C++ is that it can only be used as a part of declaration. (C99 fixed this problem by introducing compound literals. Similar functionality is coming to C++ as well.) For this reason you might see many programmers use memset in order to zero something out in the middle of C89/90 or C++ the code. Yet, I'd say that the proper way to do is still without memset but rather with something like

some_struct s;
...
{
  const some_struct ZERO = { 0 };  
  s = ZERO;
}
...

i.e. by introducing a "fictive" block in the middle of the code, even though it might not look too pretty at the first sight. Of course, in C++ there's no need to introduce a block.

As for the practical difference... You might hear some people say that memset will produce the same results in practice, since in practice the physical all-zero bit pattern is what is used to represent zero values for all types. However, this is generally not true. An immediate example that would demonstrate the difference in a typical C++ implementation is a pointer-to-data-member type

struct S;
...

int S::*p = { 0 };
assert(p == NULL); // this assertion is guaranteed to hold

memset(&p, 0, sizeof p);
assert(p == NULL); // this assertion will normally fail

This happens because a typical implementation usually uses the all-one bit pattern (0xFFFF...) to represent the null pointer of this type. The above example demonstrates a real-life practical difference between a zeroing memset and a normal = { 0 } initializer.

相关文章