如何用 0 memset 匿名联合

How to memset an anonymous union with 0

我应该如何将匿名联合清零?我在 cppreference 页面上找不到任何关于它的信息。 memset它最大的成员 0 会在这里工作吗?

例如-

#include <iostream>
#include <cstring>

struct s{
    char a;
    char b[100];
};

int main(){
 union {
   int a;
   s b;
   char c;
 };

  // b.a = 'a'; (1)

  std::memset(&b, 0, sizeof(b));

  std::cout << a << "\n";
  std::cout << b.a << " " << b.b << "\n";
  std::cout << c << "\n";
}

此外,如果这可行,我是否应该在使用 memset() 激活最大成员之前取消注释 (1)?

只需 memset 每个成员,并依靠优化器消除冗余写入。

如果你真的想尊重标准,你应该知道你写的代码是未定义的行为:C++标准§3.8[basic.life]:

... except that if the object is a union member or subobject thereof, its lifetime only begins if that union member is the initialized member in the union (8.6.1, 12.6.2), or as described in 9.3. The lifetime of an object o of type T ends when: (1.3) — if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or (1.4) — the storage which the object occupies is released, or is reused by an object that is not nested within o (1.8).

在 §9.3 中解释说,您可以通过分配给 standard-layout 联合的成员来激活它。它还解释了您可以探索只有在满足某些条件时才会激活的工会成员的价值:

If a standard-layout union contains several standard-layout structs that share a common initial sequence (9.2), and if a non-static data member of an object of this standard-layout union type is active and is one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of the standard-layout struct members; see 9.2. — end note ]

所以当你写 std::cout<< a << "\n" 时,你还没有初始化 a,也没有通过赋值激活它,也没有初始化任何成员,所以你处于 未定义行为(注意:但我知道的编译器支持它,至少在 PC 上,作为标准的扩展。)

所以在使用a之前你必须写a=0,或者让a成为union的初始化成员,因为a不共享一个共同的初始化既没有 b 也没有 c.

的序列

所以如果你使用memset,正如MSalters的答案中所建议的那样,无论你做什么,在使用它之前,你必须将一些东西分配给工会成员。如果要保持 定义的行为 ,请不要使用 memset。请注意,memset 可以安全地与非联合成员的 standard-layout 对象一起使用,因为它们的 life-time 在为它们获取存储时开始。


总而言之,要保持已定义的行为,您必须至少初始化一个成员,然后您可以检查联合体中与初始化成员共享公共初始化序列的其他成员。

  1. 如果您打算在主函数中使用匿名联合, 您可以声明联合静态:所有静态对象都是零初始化的。 (但当您回忆起 main() 不会发生的功能时,不会重新初始化):

    int main(){
     static union {
      s b;
      int a;
      char c;
      };
     //...
     }
    

    如 C++ 标准 §8.6 文章 (6.3) 中所述 [dcl.init]:

    if T is a (possibly cv-qualified) union type, the object’s first non-static named data member is zero- initialized and padding is initialized to zero bits;

  2. 否则如果结构成员之间没有填充(s),你可以用空列表聚合初始化较大的成员(s):

    //...
    int main(){
      union {
       int a;
       s b{};
       char c;
       };
      //...
      }
    

    这项工作之所以有效,是因为工会的所有成员都是一致的。因此,如果 s 的成员之间没有填充,联合内存的每个字节都将初始化为零,C++ 标准 §9.3 [class.union] 文章 2:

    The size of a union is sufficient to contain the largest of its non-static data members. Each non-static data member is allocated as if it were the sole member of a struct. [ Note: A union object and its non-static data members are pointer-interconvertible (3.9.2, 5.2.9). As a consequence, all non-static data members of a union object have the same address.

  3. 如果S内部有padding,那么就声明一个char数组来初始化:

    //...
    int main(){
      union {
       char _initialization[sizeof(s)]{};
       int a;
       s b;
       char c;
       };
      //...
      }
    

注意:使用您的示例或最后两个代码示例,并且使用 memset 的代码会生成完全相同的一组初始化指令(clang -> x86_64):

    pushq   %r14
    pushq   %rbx
    subq    0, %rsp
    xorps   %xmm0, %xmm0
    movaps  %xmm0, 96(%rsp)
    movaps  %xmm0, 80(%rsp)
    movaps  %xmm0, 64(%rsp)
    movaps  %xmm0, 48(%rsp)
    movaps  %xmm0, 32(%rsp)
    movaps  %xmm0, 16(%rsp)
    movq    [=13=], 109(%rsp)

我只是分享一个想法,也许我们可以像这样使用元编程:

template<typename T1, typename T2>
struct Bigger
{
  typedef typename std::conditional<sizeof(T1) >= sizeof(T2), T1, T2>::type Type;
};

// Recursion helper
template<typename...>
struct BiggestHelper;

// 2 or more types
template<typename T1, typename T2, typename... TArgs>
struct BiggestHelper<T1, T2, TArgs...>
{
    typedef typename Bigger<T1, typename BiggestHelper<T2, TArgs...>::Type>::Type Type;
};

// Exactly 2 types
template<typename T1, typename T2>
struct BiggestHelper<T1, T2>
{
    typedef typename Bigger<T1, T2>::Type Type;
};

// Exactly one type
template<typename T>
struct BiggestHelper<T>
{
    typedef T Type;
};

template<typename... TArgs>
struct Biggest
{
    typedef typename BiggestHelper<TArgs...>::Type Type;
};

所以在main函数中我们可以这样做:

std::memset(&b, 0, sizeof(Biggest<int,s,char>::Type));