8

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

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 访问联合的任何部分,而该部分不是最后写入的部分(写起来很麻烦,但我想不出更清晰的方式来表达这一点)我想知道代码是否以上是检查我的联合实例是否相等的有效方法?

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

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

4

5 回答 5

9

通常,您需要预先添加某种当前联合类型的指标:

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)

为了避免这个陷阱。

于 2012-08-30T11:31:20.063 回答
1

在 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));

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

于 2012-08-30T11:42:13.810 回答
0

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

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

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

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

例如

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;
};
于 2012-08-30T11:29:28.323 回答
0

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

于 2012-08-30T12:03:02.627 回答
0

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

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

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

于 2012-08-30T14:06:14.083 回答