将对象初始化为全零

时间:2010-05-14 21:51:00

标签: c++ c struct

数据结构的有效初始化通常是将所有成员设置为零。即使使用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在跟进中提出this question,我认为这是这个问题的一个有趣推论。

12 个答案:

答案 0 :(得分:23)

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

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

some_struct s = {};

而在C语言中,成语是

some_struct s = { 0 };

注意,在C中,= { 0 }可以称为通用零初始化器。它可以与几乎任何类型的对象一起使用,因为{}封闭的初始化器也允许使用标量对象

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

使= { 0 }在通用类型无关的C代码中有用(例如,与类型无关的宏)。

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

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

即。通过在代码中间引入一个“虚构”块,即使它在第一眼看起来可能看起来不太漂亮。当然,在C ++中没有必要引入一个块。

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

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

这是因为典型的实现通常使用全一位模式(0xFFFF...)来表示此类型的空指针。上面的示例演示了归零memset和普通= { 0 }初始值设定项之间的实际差异。

答案 1 :(得分:15)

some_struct s = { 0 };保证可以正常工作; memset依赖于实施细节,最好避免使用。

答案 2 :(得分:6)

如果struct包含指针,则memset生成的所有位0的值可能与在C(或C ++)代码中为其分配0的值不同,即{ {1}}指针。

NULLfloats也可能是这种情况,但我从未遇到过。但是,我不认为这些标准会保证它们在{{1}时变为零或者。)

编辑:从更实际的角度来看,我仍然会说尽可能避免使用doubles,因为它是一个额外的函数调用,写入时间更长,并且(在我看来)意图不如memset明确。

答案 3 :(得分:5)

根据编译器优化,可能存在一些阈值,其中memset更快,但通常远高于基于堆栈的变量的正常大小。在带有虚拟表的C ++对象上使用memset当然很糟糕。

答案 4 :(得分:4)

我找到了一个很好的解决方案:

template<typename T> void my_zero(T& e) {
    static T dummy_zero_object;
    e = dummy_zero_object;
}

my_zero(s);

这不仅对基本类型和用户定义类型是正确的,而且它也初始化了为其定义默认构造函数的类型,但没有初始化所有成员变量 - 尤其是包含非平凡的类{ {1}}成员。

答案 5 :(得分:3)

唯一的实用区别在于={0};语法更清楚地说“将其初始化为空”(至少对我来说似乎更清楚)。

纯粹从理论上讲,有一些情况memset可能会失败,但据我所知,它们实际上就是这样:理论上的。 OTOH,鉴于它在理论上都不如实际观点,我很难弄清楚为什么有人想要memset来完成这项任务。

答案 6 :(得分:3)

我从来没有理解将所有东西设置为零的神秘善意,即使它被定义似乎也不太可取。由于这被标记为C ++,因此初始化的正确解决方案是为结构或类提供构造。

答案 7 :(得分:3)

  

希望据了解,这仅适用于POD结构;如果该结构中存在C ++ std :: string,则会出现编译器错误。

不,不会。如果你使用memset,那么你最好只是崩溃,最糟糕的是你会得到一些胡言乱语。只要它们是聚合,= { }方式就可以非常好地用于非POD结构。 = { }方式是获取C ++的最佳方式。请注意,C ++中没有理由将0放入其中,也不推荐使用,因为它大大减少了可以使用它的情况

struct A {
  std::string a;
  int b;
};

int main() {
  A a = { 0 };
  A a = { };
}

第一个不会做你想要的:它会尝试从C字符串创建一个std::string给定一个指向其构造函数的空指针。然而,第二个做你想要的:它创建一个空字符串。

答案 8 :(得分:2)

我认为初始化更能说明你实际在做什么。您正在初始化结构。当新标准出来时,初始化的方式会得到更多的使用(用{}初始化容器是值得期待的)。 memset方式稍微容易出错,并且不能清楚地表明你正在做什么。单独编程时可能不会占很多,但在团队工作时意味着很多。

对于使用c ++,memset,malloc&amp;合。是非常深奥的生物。我自己遇到过一些。

答案 9 :(得分:2)

清除结构的最佳方法是单独设置每个字段:

struct MyStruct
{
  std::string name;
  int age;
  double checking_account_balance;
  void clear(void)
  {
     name.erase();
     age = 0;
     checking_account_balance = 0.0;
  }
};

在上面的示例中,定义了clear方法将所有成员设置为已知状态或值。由于memsetstd::fill类型,std::stringdouble方法可能无效。更强大的程序可以单独清除每个字段。

我更喜欢拥有一个更强大的计划,而不是花更少的时间打字。

答案 10 :(得分:0)

bzero功能是另一种选择。

#include <strings.h>
void bzero(void *s, size_t n);

答案 11 :(得分:0)

在C中,我更喜欢使用{0,}来表示等效的memset()。 然而gcc警告这种用法:(详情在这里: http://www.pixelbeat.org/programming/gcc/auto_init.html

在C ++中,它们通常是等效的,但与C ++一样 有一些角落要考虑(在其他答案中注明)。