在c中实现联合比较的实用方法

Practical way of implementing comparison of unions in c

出于某些测试目的,我需要比较两个并集以查看它们是否相同。将所有成员一一比较就够了吗?

union Data {
    int i;
    float f;
    char* str;
} Data;

bool isSame(union Data left, union Data right)
{
    return (left.i == right.i) && (left.f == right.f) && (left.str == right.str);
}

我的预感是,如果联合首先包含较大的类型然后切换到较小的类型,则它可能会失败。我看到一些建议提到将联合包装在一个结构中(如此处:What is the correct way to check equality between instances of a union?),该结构跟踪联合当前是哪种数据类型,但我不知道这将如何实际实施。我是否不需要在设置联合值的每个实例中手动设置联合类型?

struct myData
{
    int dataType;
    union {
        ...
    } u;
}

void someFunc()
{
    struct myData my_data_value = {0};
    my_data_value.u.i = 5;
    my_data_value.u.dataType = ENUM_TYPE_INTEGER;

    my_data_value.u.f = 5.34;
    my_data_value.u.dataType = ENUM_TYPE_FLOAT;
    
    ...
}

似乎没有必要将涉及我的联合的所有代码加倍,只是为了能够对联合值进行完美比较。我是否缺少一些明显的聪明方法来解决这个问题?

我要做的是将最大的元素相互比较(当具有 floatint 时),或者当具有指针类型时(string),比较指针的元素,所以他有这样的东西:

bool isSame(struct myData d1, struct myData d2)
{
    if(d1.dataType != d2.dataType)
        return 0; // invalid comparison
    if(d1.dataType == ENUM_TYPE_STRING)
        return !strcmp(d1.str, d2.str); // <--- compare the strings
    return d1.str == d2.str; // <--- compare either int/int or float/float
}

这将根据数据类型比较两个字符串或数字。

我说d1.str;我只是将 union 作为 unnamed union 放入 struct 中,这样您就可以从 myData struct:

中访问变量
struct myData
{
    int dataType;
    union {
        int i;
        float f;
        char *str;
    };
};

现在,我必须说我没有完全在这里回答你的问题……

您必须跟踪数据类型。如果您查看上面的 isSame 函数,我们不能将无效数据相互比较,例如字符串指针和浮点数,那是没有意义的。即使在其上使用 memcmp,它也不会比较该字符串指针处的字节,而是比较指针值本身。

所以你必须跟踪数据类型并将你的数据联合体包装到另一个结构中。

union 的棘手之处在于无法判断哪个成员是“活跃”成员。如果所有成员恰好大小相同,您可以只检查一个成员。

如果成员大小不同,您可能会遇到一些陷阱。

如果您设置一个较大的成员后跟一个较小的成员,则较大的成员使用的额外字节将保持不变。例如:

union u1 {
    unsigned int a;
    unsigned short b;
};
union u1 x,y;
x.a = 0x12345678;
y.a = 0x87654321;
x.b = 0;
y.b = 0;

逻辑上 xy 具有相同的值,但 x.a == y.a 为假而 memcmp(&x, &y, sizeof x) 将 return 非零。

只设置较小的值可能会更糟:

union u1 x,y;
x.b = 0;
y.b = 0;

由于额外的字节具有 不确定的值 并且执行 x.a == y.a 将通过尝试读取这些值来触发 undefined behavior

您需要以某种方式跟踪活跃成员以了解阅读哪一个。最简单的方法是将 union 包装在带有“标记”字段的 struct 中,这样您就知道要检查哪个。

评论中提到的 isSame 函数必须采用包含 struct 的两个实例,并使用 switch 语句来选择要检查的字段。当您调用它时,您可以使用复合文字创建要比较的结构的临时副本,即:

isSame((struct myData){ .datatype = ENUM_TYPE_INTEGER, .u = { .i= 5 }}, finalData)
isSame((struct myData){ .datatype = ENUM_TYPE_FLOAT, .u = { .f= 5.34 }}, finalData)

Is it enough to compare all members one by one?

取决于你如何比较。
考虑:

union Data {
    float f;
} Data;

if (a.f == b.f) ....
a.f 为 +0.0 且 b.f 为 -0.0 时,

a.f == b.f 为真。
a.fb.f 为 NaN 时,a.f == b.f 为假,即使具有相同的位模式。

最好使用 memcmp()


It does not seem warranted to double all code where my union is involved simply to be able to make a perfect comparison of the union values.

测试代码。不用担心所有代码加倍。做一个完美的比较。


Am I missing some obvious smart way to go about this?

将最宽的类型与 memcmp() 进行比较就足够了。

目前尚不清楚 OP 希望如何处理较窄领域中遗留的垃圾。 IMO,memcmp() 比较应该只发生在最后一个成员分配上,如 .dataType 所示。

如果您的建议有效,那么您可以使用 memcmp(&left, &right, sizeof left) 实现相同的效果,而无需多次比较。但这行不通,你的提议也行不通,原因相同。

首先,分配给不占用分配给联合的所有字节的联合成员对未占用的字节有未指定的影响。最有可能的是它们不会从它们以前的值进行修改,但任何值都是可能的。比较这些字节的值有一个未指定的结果。

您可能认为在分配成员之前将联合的字节 memsetting 为 0 将允许比较工作,但标准不要求未使用的字节是未修改的。此外,许多编译器会优化清除联合的尝试,理由是如果下一条语句为联合赋予新值,它没有法律效力。

尝试比较非当前值的联合成员还有其他原因,即使两个值都不包含填充,这些原因也适用。

例如,如果您不知道这两个联合值当前具有相同的活动成员,您可能会得到错误的等价。 (每个 float 与某些 int 具有相同的位模式,但两个值肯定不相同。)

不太明显的是,具有不同位模式的两个值实际上可能相等。 (例如,浮点数 0.0 和 -0.0 被认为是相等的。)

最后,并非每个位模式都是有效的float;如果一个或两个联合值是 int,其位模式对应于浮动 NaN,则尝试将值与 floats 进行比较肯定会产生错误的答案(NaN 不等于自身) 并可能抛出浮点异常。

简而言之,如果您不知道哪种类型对联合有效,则无法有效地使用联合值,除非将其分配给同一联合类型的另一个对象。这意味着必须有某种内部或外部机制来标识联合的活动类型。

外部机制(例如,在 yacc 生成的解析器中使用)和内部机制(所谓的“区分联合”,正如您在问题末尾建议的那样)之间的选择将取决于精确的应用环境。

如果您不知道在对两个变量的最后一次赋值中使用了哪个选择器,则无法比较两个 union。比较两个联合(正确)的唯一方法是使用相同的选择器字段分配它们并具有相同的值,使用可用于该选择器类型的比较。

比方说,你有:

union data {
    int i;
    float f;
};

并且您有两个变量 AB,它们是这样分配的:

A.i = 0x80000000; /* the integer value -2147483648 */
B.f = 0.0; /* the float value 0.0 */

首先,使用float选择器,用IEEE-722二进制浮点表示,它们可以与truefalse进行比较,因为A.i被重新解释为a float-0.0,如果比较 A.f == B.f,它与 B.f 匹配(如浮点数 -0.0 == +0.0)。但如果你比较 A.i == B.i,它们将与 false 进行比较,(A.i 应该是 -2147483648,而 B.i 应该是 0) 二值图像确实不一样。所以你需要知道在上次赋值中使用了哪个字段选择器。

此外,假设您有:

union data {
    char c;
    char s[100];
}

并假设您像以前一样有已分配的变量 AB

    strcpy(A.s, "hello, kitty");
    strcpy(B.s, "hello, world");

如果将它们与 A.c == B.c 进行比较,它们将与 true 进行比较,因为两个字符串中的第一个字符相同。但是 false 如果将它们与 strcmp(A.s, B.s) == 0 进行比较(在这种情况下,这应该是比较它们的正确方法)

更多,因为如果我们后面做A.c = 'H';B.c = 'H';,那么如果我们使用A.c == B.c,他们会比较true(现在应该是正确的方法比较),而如果我们使用 strcmp(A.s, B.s) == 0,他们将比较 false。无论如何,第二个选择器(char [100] 类型的选择器)可以作为字符数组(按字典顺序或任何其他类型的排序规则)或作为字符串(空分隔)进行比较,给出不同的结果,具体取决于历史他们有过的任务。

最后,假设您有:

union data {
    struct {
        char a1; /* compiler should pad 3 byte space before next field */
        int[100] a2; /* compiler can pad 4 byte space space before next field */
        double[23] a3;
    } a;
    struct {
        double[12] b1; 
        char b2; /* compiler could pad 3 bytes space before next field */
        int b3[100];
    } b;
};

那我们应该怎么比较呢? (认为​​一个选择器中的填充孔可以是另一个选择器中的有效数据。)