C如何克服别名限制(联合体?)

8
假设我有一个样例源文件test.c,我正在像这样编译它:

$ gcc -03 -Wall

test.c的内容大致如下..
/// CMP128(x, y)
//
// arguments
//  x - any pointer to an 128-bit int
//  y - any pointer to an 128-bit int
//
// returns -1, 0, or 1 if x is less than, equal to, or greater than y
//
#define CMP128(x, y) // magic goes here

// example usages

uint8_t  A[16];
uint16_t B[8];
uint32_t C[4];
uint64_t D[2];
struct in6_addr E;
uint8_t* F;

// use CMP128 on any combination of pointers to 128-bit ints, i.e.

CMP128(A, B);
CMP128(&C[0], &D[0]);
CMP128(&E, F);

// and so on

假设我接受这样的限制:如果传入两个重叠的指针,则会产生未定义的结果。

我尝试了类似于以下内容的东西(请想象这些宏的格式已经正确,每行末尾都有反斜杠转义换行符)

#define CMP128(x, y) ({
  uint64_t* a = (void*)x;
    uint64_t* b = (void*)y;

  // compare a[0] with b[0], a[1] with b[1]
})

但是当我在宏中取消引用a(a[0] < b[0])时,gcc会报告“取消引用违反了严格别名规则”的错误。

我原本认为应该使用union来正确地将同一内存位置以两种不同的方式引用,因此接下来我尝试了类似以下的代码:

#define CMP128(x, y) ({
    union {
        typeof(x) a;
        typeof(y) b;
        uint64_t* c;
    }   d = { .a = (x) }
        , e = { .b = (y) };

    // compare d.c[0] with e.c[0], etc
})

除了编译器对于strict-aliasing规则的精确相同的错误之外,我没有别的办法。

那么:有没有什么方法可以在不违反strict-aliasing的情况下完成这个操作,而不是实际上复制内存呢?

may_alias不算,它只允许您绕过strict-aliasing规则)

编辑:使用memcmp来完成这项任务。我被别名规则所困扰,并没有想到这一点。


1
为了以符合标准的方式使用联合体,您只允许从您最后写入的成员中读取。 - Kerrek SB
8
@Kerrek:不正确 - C99允许使用联合体进行类型切换,一个明确提到这一点的脚注已经在TC3中添加了;但是,Todd的代码仍然是不正确的... - Christoph
@Christoph:C1x更好了,附录J(UB)已经修复,允许读取与最后写入成员对应的字节(显然这是C99中的目的,但显然附录J当时被忽视了)。 - ninjalj
typeof(x) 是 GCC 的扩展。我们这里不讨论标准 C,因此从“符合标准”的角度推理是无用的。 - user824425
1个回答

5
编译器是正确的,因为别名规则由所谓的“有效类型”决定,即你正在访问的对象(即内存位置),而不管任何指针操作。在这种情况下,使用联合体来进行类型切换与显式转换没有区别 - 使用显式转换实际上更好,因为标准不能保证任意指针类型具有兼容的表示,即您不必要地依赖于实现定义的行为。
如果您想符合标准,需要将数据复制到新变量中或在原始变量的声明期间使用联合体。
如果您的128位整数是大端或小端(即不是混合端),您还可以使用memcmp()(直接或在取反返回值后)或自己进行逐字节比较:通过字符类型的指针访问是别名规则的例外。

1
我应该想到memcmp。那基本上就是我想做的。 - Todd Freed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接