这个 C++ 结构初始化技巧安全吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/112085/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 12:55:35  来源:igfitidea点击:

Is this C++ structure initialization trick safe?

c++data-structures

提问by Rob

Instead of having to remember to initialize a simple 'C' structure, I might derive from it and zero it in the constructor like this:

不必记住初始化一个简单的“C”结构,我可以从中派生并在构造函数中将其归零,如下所示:

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct()
    {
        memset(this, 0, sizeof(MY_STRUCT));
    }
};

This trick is often used to initialize Win32 structures and can sometimes set the ubiquitous cbSizemember.

这个技巧通常用于初始化 Win32 结构,有时可以设置无处不在的cbSize成员。

Now, as long as there isn't a virtual function table for the memset call to destroy, is this a safe practice?

现在,只要没有用于 memset 调用的虚函数表来销毁,这是一种安全的做法吗?

采纳答案by paercebal

PREAMBLE:

前言:

While my answer is still Ok, I find litb's answerquite superior to mine because:

虽然我的答案还可以,但我发现litb 的答案比我的要好,因为:

  1. It teaches me a trick that I did not know (litb's answers usually have this effect, but this is the first time I write it down)
  2. It answers exactly the question (that is, initializing the original struct's part to zero)
  1. 它教会了我一个我不知道的技巧(litb的答案通常有这个效果,但这是我第一次写下来)
  2. 它准确地回答了这个问题(即将原始结构的部分初始化为零)

So please, consider litb's answer before mine. In fact, I suggest the question's author to consider litb's answer as the right one.

所以请在我之前考虑 litb 的回答。事实上,我建议问题的作者将 litb 的答案视为正确的答案。

Original answer

原答案

Putting a true object (i.e. std::string) etc. inside will break, because the true object will be initialized before the memset, and then, overwritten by zeroes.

将真正的对象(即 std::string)等放在里面会中断,因为真正的对象将在 memset 之前初始化,然后被零覆盖。

Using the initialization list doesn't work for g++ (I'm surprised...). Initialize it instead in the CMyStruct constructor body. It will be C++ friendly:

使用初始化列表对 g++ 不起作用(我很惊讶......)。而是在 CMyStruct 构造函数体中初始化它。它将是 C++ 友好的:

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct() { n1 = 0 ; n2 = 0 ; }
};

P.S.: I assumed you did have nocontrol over MY_STRUCT, of course. With control, you would have added the constructor directly inside MY_STRUCT and forgotten about inheritance. Note that you can add non-virtual methods to a C-like struct, and still have it behave as a struct.

PS:当然,我假设您确实无法控制 MY_STRUCT。通过控制,您可以直接在 MY_STRUCT 中添加构造函数而忘记继承。请注意,您可以将非虚拟方法添加到类似 C 的结构中,并且仍然使其表现为结构。

EDIT: Added missing parenthesis, after Lou Franco's comment. Thanks!

编辑:在 Lou Franco 的评论之后添加了缺少的括号。谢谢!

EDIT 2 : I tried the code on g++, and for some reason, using the initialization list does not work. I corrected the code using the body constructor. The solution is still valid, though.

编辑 2:我在 g++ 上尝试了代码,由于某种原因,使用初始化列表不起作用。我使用主体构造函数更正了代码。不过,该解决方案仍然有效。

Please reevaluate my post, as the original code was changed (see changelog for more info).

请重新评估我的帖子,因为原始代码已更改(有关更多信息,请参阅更改日志)。

EDIT 3 : After reading Rob's comment, I guess he has a point worthy of discussion: "Agreed, but this could be an enormous Win32 structure which may change with a new SDK, so a memset is future proof."

编辑 3:阅读 Rob 的评论后,我猜他有一个值得讨论的观点:“同意,但这可能是一个巨大的 Win32 结构,它可能会随着新的 SDK 发生变化,因此 memset 是未来的证明。”

I disagree: Knowing Microsoft, it won't change because of their need for perfect backward compatibility. They will create instead an extended MY_STRUCTExstruct with the same initial layout as MY_STRUCT, with additionnal members at the end, and recognizable through a "size" member variable like the struct used for a RegisterWindow, IIRC.

我不同意:了解微软,它不会因为他们需要完美的向后兼容性而改变。相反,他们将创建一个扩展的 MY_STRUCT Ex结构,其初始布局与 MY_STRUCT 相同,末尾有附加成员,并且可以通过“大小”成员变量识别,如用于 RegisterWindow,IIRC 的结构。

So the only valid point remaining from Rob's comment is the "enormous" struct. In this case, perhaps a memset is more convenient, but you will have to make MY_STRUCT a variable member of CMyStruct instead of inheriting from it.

因此,Rob 的评论中唯一有效的一点是“巨大的”结构。在这种情况下,也许 memset 更方便,但您必须使 MY_STRUCT 成为 CMyStruct 的变量成员,而不是从它继承。

I see another hack, but I guess this would break because of possible struct alignment problem.

我看到另一个 hack,但我想这会因为可能的结构对齐问题而中断。

EDIT 4: Please take a look at Frank Krueger's solution. I can't promise it's portable (I guess it is), but it is still interesting from a technical viewpoint because it shows one case where, in C++, the "this" pointer "address" moves from its base class to its inherited class.

编辑 4:请查看 Frank Krueger 的解决方案。我不能保证它是可移植的(我猜是),但从技术角度来看它仍然很有趣,因为它显示了一种情况,在 C++ 中,“this”指针“地址”从其基类移动到其继承类.

回答by Johannes Schaub - litb

You can simply value-initialize the base, and all its members will be zero'ed out. This is guaranteed

您可以简单地对基进行值初始化,它的所有成员都将被清零。这是有保证的

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct():MY_STRUCT() { }
};

For this to work, there should be no user declared constructor in the base class, like in your example.

为此,基类中不应该有用户声明的构造函数,就像在您的示例中一样。

No nasty memsetfor that. It's not guaranteed that memsetworks in your code, even though it should work in practice.

没有什么讨厌memset的。不能保证memset在您的代码中有效,即使它在实践中应该有效。

回答by Drealmer

Much better than a memset, you can use this little trick instead:

比 memset 好得多,你可以使用这个小技巧:

MY_STRUCT foo = { 0 };

This will initialize all members to 0 (or their default value iirc), no need to specifiy a value for each.

这会将所有成员初始化为 0(或它们的默认值 iirc),无需为每个成员指定一个值。

回答by Frank Krueger

This would make me feel much safer as it should work even if there is a vtable(or the compiler will scream).

这会让我感觉更安全,因为即使有vtable(或者编译器会尖叫)它也应该工作。

memset(static_cast<MY_STRUCT*>(this), 0, sizeof(MY_STRUCT));

I'm sure your solution will work, but I doubt there are any guarantees to be made when mixing memsetand classes.

我确信您的解决方案会起作用,但我怀疑在混合memset和类时是否有任何保证。

回答by Benoit

This is a perfect example of porting a C idiom to C++ (and why it might not always work...)

这是将 C 习语移植到 C++ 的完美示例(以及为什么它可能并不总是有效......)

The problem you will have with using memset is that in C++, a struct and a class are exactly the same thing except that by default, a struct has public visibility and a class has private visibility.

使用 memset 会遇到的问题是,在 C++ 中,结构和类是完全相同的东西,除了默认情况下,结构具有公共可见性而类具有私有可见性。

Thus, what if later on, some well meaning programmer changes MY_STRUCT like so:

因此,如果稍后,一些善意的程序员像这样更改 MY_STRUCT 会怎样:


struct MY_STRUCT
{
    int n1;
    int n2;

   // Provide a default implementation...
   virtual int add() {return n1 + n2;}  
};

By adding that single function, your memset might now cause havoc. There is a detailed discussion in comp.lang.c+

通过添加单个函数,您的 memset 现在可能会造成严重破坏。comp.lang.c+中有详细讨论

回答by Richard Corden

The examples have "unspecified behaviour".

这些示例具有“未指定的行为”。

For a non-POD, the order by which the compiler lays out an object (all bases classes and members) is unspecified (ISO C++ 10/3). Consider the following:

对于非 POD,编译器布置对象(所有基类和成员)的顺序未指定 (ISO C++ 10/3)。考虑以下:

struct A {
  int i;
};

class B : public A {       // 'B' is not a POD
public:
  B ();

private:
  int j;
};

This can be laid out as:

这可以布置为:

[ int i ][ int j ]

Or as:

或作为:

[ int j ][ int i ]

Therefore, using memset directly on the address of 'this' is very much unspecified behaviour. One of the answers above, at first glance looks to be safer:

因此,直接在“this”的地址上使用 memset 是非常未指定的行为。上面的答案之一,乍一看似乎更安全:

 memset(static_cast<MY_STRUCT*>(this), 0, sizeof(MY_STRUCT));

I believe, however, that strictly speaking this too results in unspecified behaviour. I cannot find the normative text, however the note in 10/5 says: "A base class subobject may have a layout (3.7) different from the layout of a most derived object of the same type".

然而,我相信,严格来说,这也会导致未指明的行为。我找不到规范文本,但是 10/5 中的注释说:“基类子对象的布局 (3.7) 可能与同一类型的最派生对象的布局不同”。

As a result, I compiler could perform space optimizations with the different members:

因此,我的编译器可以对不同的成员执行空间优化:

struct A {
  char c1;
};

struct B {
  char c2;
  char c3;
  char c4;
  int i;
};

class C : public A, public B
{
public:
  C () 
  :  c1 (10);
  {
    memset(static_cast<B*>(this), 0, sizeof(B));      
  }
};

Can be laid out as:

可以布置为:

[ char c1 ] [ char c2, char c3, char c4, int i ]

On a 32 bit system, due to alighments etc. for 'B', sizeof(B) will most likely be 8 bytes. However, sizeof(C) can also be '8' bytes if the compiler packs the data members. Therefore the call to memset might overwrite the value given to 'c1'.

在 32 位系统上,由于 'B' 的对齐等,sizeof(B) 很可能是 8 个字节。但是,如果编译器打包数据成员,则 sizeof(C) 也可以是 '8' 字节。因此,对 memset 的调用可能会覆盖给 'c1' 的值。

回答by Florian B?sch

Precise layout of a class or structure is not guaranteed in C++, which is why you should not make assumptions about the size of it from the outside (that means if you're not a compiler).

在 C++ 中不能保证类或结构的精确布局,这就是为什么您不应该从外部对它的大小做出假设(这意味着如果您不是编译器)。

Probably it works, until you find a compiler on which it doesn't, or you throw some vtable into the mix.

可能它可以工作,直到您找到一个没有它的编译器,或者您将一些 vtable 混入其中。

回答by Lou Franco

If you already have a constructor, why not just initialize it there with n1=0; n2=0; -- that's certainly the more normalway.

如果你已经有一个构造函数,为什么不直接用 n1=0 初始化它呢?n2=0;——这当然是更正常的方式。

Edit: Actually, as paercebal has shown, ctor initialization is even better.

编辑:实际上,正如paercebal 所示,ctor 初始化更好。

回答by MSalters

From an ISO C++ viewpoint, there are two issues:

从 ISO C++ 的角度来看,有两个问题:

(1) Is the object a POD? The acronym stands for Plain Old Data, and the standard enumerates what you can't have in a POD (Wikipedia has a good summary). If it's not a POD, you can't memset it.

(1) 对象是 POD 吗?首字母缩写词代表Plain Old Data,标准列举了POD 中没有的内容(维基百科有一个很好的总结)。如果它不是 POD,则无法对其进行 memset。

(2) Are there members for which all-bits-zero is invalid ? On Windows and Unix, the NULL pointer is all bits zero; it need not be. Floating point 0 has all bits zero in IEEE754, which is quite common, and on x86.

(2) 是否存在所有位为零无效的成员?在 Windows 和 Unix 上,NULL 指针全为零;不必如此。浮点 0 在 IEEE754 和 x86 上的所有位都为零,这很常见。

Frank Kruegers tip addresses your concerns by restricting the memset to the POD base of the non-POD class.

Frank Kruegers 提示通过将 memset 限制为非 POD 类的 POD 基础来解决您的问题。

回答by Thanatopsis

Try this - overload new.

试试这个 - 重载新的。

EDIT: I should add - This is safe because the memory is zeroed before anyconstructors are called. Big flaw - only works if object is dynamically allocated.

编辑:我应该添加 - 这是安全的,因为在调用任何构造函数之前内存已清零。大缺陷 - 只有在动态分配对象时才有效。

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct()
    {
        // whatever
    }
    void* new(size_t size)
    {
        // dangerous
        return memset(malloc(size),0,size);
        // better
        if (void *p = malloc(size))
        {
            return (memset(p, 0, size));
        }
        else
        {
            throw bad_alloc();
        }
    }
    void delete(void *p, size_t size)
    {
        free(p);
    }

};