memcpy/memmove成为工会会员,这是否设置了“活动"会员? [英] memcpy/memmove to a union member, does this set the 'active' member?

查看:115
本文介绍了memcpy/memmove成为工会会员,这是否设置了“活动"会员?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

重要的说明:一些评论者似乎认为我是从工会那里抄袭过来的.仔细查看memcpy,它从普通的uint32_t的地址复制而来,该地址不包含在联合中.另外,我(通过memcpy)复制到工会的特定成员(u.a16&u.x_in_a_union),而不是直接复制到整个工会本身(&u)

Important clarification: some commenters seem to think that I am copying from a union. Look carefully at the memcpy, it copies from the address of a plain old uint32_t, which is not contained within a union. Also, I am copying (via memcpy) to a specific member of a union (u.a16 or &u.x_in_a_union, not directly to the entire union itself (&u)

C ++对联合非常严格-仅当该成员是最后一个写入成员时,才应阅读该成员:

C++ is quite strict about unions - you should read from a member only if that was the last member that was written to:

9.5联合[class.union] [[c ++ 11]] 在联合中,最多可以随时激活一个非静态数据成员,即,最多可以随时将一个非静态数据成员的值存储在一个联合中.

9.5 Unions [class.union] [[c++11]] In a union, at most one of the non-static data members can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time.

(当然,编译器不会跟踪哪个成员处于活动状态.开发人员应确保他们自己对此进行跟踪)

(Of course, the compiler doesn't track which member is active. It's up to the developer to ensure they track this themselves)

更新:以下代码块是主要问题,直接反映问题标题中的文本.如果这段代码可以,那么我会跟进其他类型的代码,但是现在我意识到这第一段代码本身很有趣.

#include <cstdint>
uint32_t x = 0x12345678;
union {
    double whatever;
    uint32_t x_in_a_union; // same type as x
} u;
u.whatever = 3.14;
u.x_in_a_union = x; // surely this is OK, despite involving the inactive member?
std::cout << u.x_in_a_union;
u.whatever = 3.14; // make the double 'active' again
memcpy(&u.x_in_a_union, &x); // same types, so should be OK?
std::cout << u.x_in_a_union; // OK here? What's the active member?

紧接其上方的代码块可能是注释和答案中的主要问题.事后看来,我不需要在这个问题中混用类型!基本上,假设类型相同,u.a = b是否与memcpy(&u.a,&b, sizeof(b))相同?

首先,一个相对简单的memcpy允许我们将uint32_t读取为uint16_t的数组:

First, a relatively simple memcpy allowing us to read a uint32_t as an array of uint16_t:

#include <cstdint> # to ensure we have standard versions of these two types
uint32_t x = 0x12345678;
uint16_t a16[2];
static_assert(sizeof(x) == sizeof(a16), "");
std:: memcpy(a16, &x, sizeof(x));

精确的行为取决于平台的字节序,并且必须提防陷阱表示等等.但是,这里普遍同意(我认为?反馈表示赞赏!),为避免出现有问题的值,上述代码可以在正确的平台上的正确上下文中完美地符合标准.

The precise behaviour depends on the endianness of your platform, and you must beware of trap representations and so on. But it is generally agreed here (I think? Feedback appreciated!) that, with care to avoid problematic values, the above code can be perfectly standards-complaint in the right context on the right platform.

(如果您对上述代码有疑问,请相应地注释或编辑该问题.在继续下面的有趣"代码之前,我想确保我们具有上述版本的无争议版本. )

如果,并且仅当时,以上两个代码块都不是UB,那么我想将它们组合如下:

If, and only if, both blocks of code above are not-UB, then I would like to combine them as follows:

uint32_t x = 0x12345678;
union {
    double whatever;
    uint16_t a16[2];
} u;
u.whatever = 3.14; // sets the 'active' member
static_assert(sizeof(u.a16) == sizeof(x)); //any other checks I should do?
std:: memcpy(u.a16, &x, sizeof(x));

// what is the 'active member' of u now, after the memcpy?
cout << u.a16[0] << ' ' << u.a16[1] << endl; // i.e. is this OK?

工会的哪个成员u.whateveru.a16是活动成员"?

Which member of the union, u.whatever or u.a16 , is the 'active member'?

最后,我自己的猜测是,在实践中我们关心这一点的原因是,优化的编译器可能没有注意到memcpy的发生,因此做出了错误的假设(但根据标准,这是允许的假设)关于哪个成员处于活动状态以及哪些数据类型处于活动"状态,因此会导致别名错误.编译器可能会以奇怪的方式对memcpy进行重新排序. 这是我们为什么关心这个问题的适当摘要吗?

Finally, my own guess is that the reason why we care about this, in practice, is that an optimizing compiler might fail to notice that the memcpy happened and therefore make false assumptions (but allowable assumptions, by the standard) about which member is active and which data types are 'active', therefore leading to mistakes around aliasing. The compiler might reorder the memcpy in strange ways. Is this an appropriate summary of why we care about this?

推荐答案

我对标准的理解是,只要类型可轻松复制std::memcpy是安全的.

My reading of the standard is that std::memcpy is safe whenever the type is trivially copyable.

从9个类中,我们可以看到union是类类型,因此可轻松复制应用于它们.

From 9 Classes, we can see that unions are class types and so trivially copyable applies to them.

一个 union 是一个用 class-key 联合定义的类;一次仅包含一个数据成员(9.5).

A union is a class defined with the class-key union; it holds only one data member at a time (9.5).

一个可复制的类是一个类:

  • 没有非平凡的副本构造函数(12.8),
  • 没有平凡的move构造函数(12.8),
  • 没有非平凡的副本分配运算符(13.5.3,12.8),
  • 没有非平凡的移动分配运算符(13.5.3、12.8),并且
  • 具有微不足道的析构函数(12.4).
  • has no non-trivial copy constructors (12.8),
  • has no non-trivial move constructors (12.8),
  • has no non-trivial copy assignment operators (13.5.3, 12.8),
  • has no non-trivial move assignment operators (13.5.3, 12.8), and
  • has a trivial destructor (12.4).

可简单复制的确切含义在3.9类型中给出:

The exact meaning of trivially copyable is given in 3.9 Types:

对于任何可复制类型为T的对象(基类子对象除外),无论该对象是否具有类型为T的有效值,组成该对象的基础字节(1.7)都可以是复制到charunsigned char的数组中.如果将charunsigned char数组的内容复制回该对象,则该对象随后应保留其原始值.

For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array of char or unsigned char. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value.

对于任何普通可复制类型T,如果两个指向T的指针指向不同的T对象obj1obj2,其中obj1obj2都不是基类子对象,如果将构成obj1的基础字节(1.7)复制到obj2,则obj2随后应保持与obj1相同的值.

For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the underlying bytes (1.7) making up obj1 are copied into obj2, obj2 shall subsequently hold the same value as obj1.

该标准还给出了两者的明确示例.

The standard also gives an explicit example of both.

因此,如果您要复制整个联合,则答案必定是肯定的,活动成员将与数据一起复制". (这很相关,因为它表明必须将std::memcpy 视为更改联合的活动元素的有效方法,因为明确允许整个联合复制使用它.)

So, if you were copying the entire union, the answer would be unequivocally yes, the active member will be "copied" along with the data. (This is relevant because it indicates that std::memcpy must be regarded as a valid means of changing the active element of a union, since using it is explicitly allowed for whole union copying.)

现在,您正在复制到联合的成员.该标准似乎不需要分配给工会成员的任何特定方法(因此使其处于活动状态).它所做的就是指定(9.5)

Now, you are instead copying into a member of the union. The standard doesn't appear to require any particular method of assigning to a union member (and hence making it active). All it does is specify (9.5) that

[注意:通常,必须使用显式析构函数类并放置新的运算符来更改联合的活动成员. —尾注]

[ Note: In general, one must use explicit destructor class and placement new operators to change the active member of a union. — end note]

之所以说

,是因为C ++ 11允许在联合中使用非平凡类型的对象.注意前面的一般",它很清楚地表明,在某些情况下,允许使用其他方法来更改现役成员;我们已经知道情况确实如此,因为明确允许分配.当然,没有禁止使用std::memcpy的地方,否则该使用才有效.

which it says, of course, because C++11 allows objects of non-trivial type in unions. Note the "in general" on the front, which quite clearly indicates that other methods of changing the active member are permissible in specific cases; we already know this to be the case because assignment is clearly permitted. Certainly there is no prohibition on using std::memcpy, where its use would otherwise be valid.

所以我的回答是,这是安全的,是的,它更改了活动成员.

So my answer is yes, this is safe, and yes, it changes the active member.

这篇关于memcpy/memmove成为工会会员,这是否设置了“活动"会员?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆