检查联合实例之间是不是相等的正确方法是啥?

Posted

技术标签:

【中文标题】检查联合实例之间是不是相等的正确方法是啥?【英文标题】:What is the correct way to check equality between instances of a union?检查联合实例之间是否相等的正确方法是什么? 【发布时间】:2012-08-30 11:24:23 【问题描述】:

我有一个多线程应用程序,它将数据存储为以下联合的实例数组

union unMember 
    float fData;
    unsigned int uiData;
;

存储此数组的对象知道联合中的数据是什么类型,因此在检索正确类型时我不会遇到 UB 问题。但是在程序的其他部分,我需要测试这些联合的 2 个实例之间的相等性,并且在这部分代码中,真正的内部数据类型是未知的。这样做的结果是我无法使用这种方法测试联合的相等性

  unMember un1;
  unMember un2;
  if (un1 == un2) 
     // do stuff
  

当我得到编译器错误时。因此,我只是比较联合的浮动部分

  if (un1.fData == un2.fData) 
     // compiles but is it valid?
  

现在鉴于我已经读到它是 UB 访问联合的任何部分,而该部分不是最后写入的部分(写起来很麻烦,但我想不出更清晰的说法)我想知道如果上面的代码是检查我的联合实例是否相等的有效方法??

这让我意识到,在内部我不知道工会是如何运作的。我曾假设数据只是存储为位模式,并且您可以根据联合中列出的类型以您喜欢的任何方式解释它。 如果不是这种情况,那么测试联合的 2 个实例是否相等的安全/正确方法是什么?

最后,我的应用程序是用 C++ 编写的,但我意识到联合也是 C 的一部分,那么这两种语言对它们的处理方式有什么不同吗?

【问题讨论】:

将联合包装在一些结构中,以记住上次写入的字段。 至于位模式,是的,您是对的,但请记住,并非所有位模式都是有效的浮点数。此外,如果您在联合中有一个char 和一个long,那么写入char 不会导致long 包含与long 相同的数字,因为long 有可能是任何东西的额外位。 @JoachimPileborg 是的,你是对的 - 我应该指定这个问题专门针对这种情况,我的两种类型的长度都是 4 个字节。我明白你在说什么。 【参考方案1】:

一般来说,您需要在前面添加一些当前联合类型的指示符:

struct myData

    int dataType;
    union 
        ...
     u;

然后:

if (un1.dataType != un2.dataType)
    return (1 == 0);
switch(un1.dataType)

    case TYPE_1:
        return (un1.u.type1 == un2.u.type1);
    case TYPE_2:
        ...

不管怎样,语法

if (un1.fData == un2.fData) 
    // compiles but is it valid?

确实可以编译并且有效,但由于两个原因可能无法正常工作。一个是,正如你所说,也许 un2 包含一个整数而不是一个浮点数。但在这种情况下,相等性测试通常无论如何都会失败。第二个是两个结构都包含一个浮点数,它们表示相同的数字,但有轻微的机器错误。然后测试会告诉你数字是不同的(它们是一点一点的),而它们的“含义”是相同的。

通常比较浮点数

if (dabs(f1 - f2) < error)

为了避免这个陷阱。

【讨论】:

【参考方案2】:

在 C++ 中,不是最后写入的成员被认为是未初始化的(因此读取它们是未定义的行为)。在 C 中,它们被认为包含被写入成员的 对象表示,这可能是也可能不是有效的对象表示。

也就是说,

union U 
    S x;
    T y;
 u;
u.x = 0;
T t = u.y;    // C++ - reading uninitialized memory - could crash
T t = u.y;    /* C - reading object representation of u.x - could crash */

在实践中,如果代码与编写已分配成员的代码足够远程,则 C++ 读取联合未分配成员的行为将与 C 相同,因为编译器的唯一方法是生成行为不同的代码是为了优化读写组合。

两种语言中的安全 方法(保证不会崩溃)是将内存内容作为char 的数组进行比较,例如使用memcmp:

union U u1, u2;
u1.x = 0;
u2.x = 0;

memcmp(&u1, &u2, sizeof(union U));

然而,这可能并不能反映工会成员的实际平等;例如对于浮点类型,两个NaN 可以值具有相同 内存表示并且比较不相等,而-0.00.0(负零和正零)具有不同内存表示,但比较相等。还有一个问题是这两种类型的大小不同,或者包含不参与值的位(填充位,在大多数现代商品平台上不是问题)。此外,结构类型可以包含用于对齐的填充。

【讨论】:

在 C 中,使用 memcmp 时,填充字节可能会导致错误的结果。在局部变量中,这些字节未初始化,并且包含一些来自堆栈的垃圾。 @User1 最初的问题是关于原始类型的。你说得对,填充字节是结构类型的问题。 如果联合是使用打包字节创建的,memcmp() 是否仍然有效? @Kingsley 它不会崩溃,但您可能得不到有用(甚至一致)的答案。另见***.com/questions/29349293/…【参考方案3】:

不同的类型可能有不同的存储长度(两个字节与四个字节)。

当一个工会成员被写入时,所有可以保证的是写入的成员是正确的。

如果你比较不同的成员,你不知道额外的字节是什么。

测试联合相等性的正确方法是拥有一个结构,其中包含联合和指示当前正在使用的成员的成员,并打开该成员,其中切换的情况处理每个可能的相等性检查工会成员,例如您必须将使用中的信息与 union 一起存储。

例如

enum test_enum

  TEST_ENUM_INT,
  TEST_ENUM_FLOAT
;

union test_union

  int
    test_int;

  float
    test_float;
;

struct test_struct

  enum test_enum
    te;

  union test_union
    tu;
;

【讨论】:

【参考方案4】:

我认为如果你实现一个类是最安全的。如果构造不提供功能(在这种情况下自动确定要评估的正确成员),那么构造可能不适合您的需要,您应该使用另一个构造;)这可能是自定义类,或者可能是VARIANT 如果您使用 COM(基本上是 @lserni 提出的结构)。

【讨论】:

【参考方案5】:

一般来说,你问的是不可能的。只有您设置的变量中的内存才能保证是您所期望的。另一个内存基本上是随机的。 但是,在您的情况下,您可以比较它,因为所有内容的大小都是相同的。如果我这样做,我只会比较无符号整数或做一个 memcmp。这一切都依赖于工会的所有成员都具有相同规模这一事实。例如,如果您添加了双倍投注,所有投注都将取消。这属于您可以在 C/C++ 中做和逃脱的小玩意,但维护起来要困难得多。您正在对联合进行假设,并且需要在代码中明确您做出此假设。未来的维护者可能会破坏它并导致各种难以调试的问题。

最好的办法是有一个带有类型标志的结构或使用类似Boost Variant 的东西。当使用这样的东西时,您将在未来证明自己并使用未来的维护者有机会知道或可以查找文档的标准代码。

另一个注意事项,您必须定义浮点数的相等含义。如果您想要模糊比较,那么您当然需要知道类型。如果您想要逐位比较,那么这很容易。

【讨论】:

是位意义上的相等,而不是数字意义上的相等。 然后你可以使用 memcmp 但它很危险并且需要在代码中使用好的 cmets。如果你曾经搬到一个新平台,你必须检查 sizeof(unsigned) == sizeof(float)。这是有风险的,如果有问题就很难追查。像 boost 变体或您自己的带有 typefield 的类这样的东西要安全得多

以上是关于检查联合实例之间是不是相等的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在多个数据库上选择相同查询并将它们联合的规范方法是啥?

ado.net 和 dapper 之间的交集/联合是啥?

c++ 中空类的大小和联合、结构和类之间的区别是啥?

在 Swift 中检查 2 个固定大小的数组是不是相等的最快方法是啥?

打字稿:检查类型是不是为联合

检查两个文件是不是相等的最快哈希算法是啥?