海湾合作委员会,严格的别名,并通过工会铸造

你有什么恐怖的故事要讲? GCC手册最近增加了一个关于“严格别名”和通过工会投射指针的警告:

[…]考虑地址,转换结果指针和解引用结果具有未定义的行为 [强调添加],即使转换使用联合types,例如:

union a_union { int i; double d; }; int f() { double d = 3.0; return ((union a_union *)&d)->i; } 

有没有人有一个例子来说明这个未定义的行为?

注意这个问题不是关于什么C99标准说或不说。 这是关于今天gcc和其他现有编译器的实际function。

我只是猜测,但是一个潜在的问题可能在于将d设置为3.0。 因为d是一个永远不会被直接读取的临时variables,并且永远不会通过“有点兼容的”指针读取,所以编译器可能不费心去设置它。 然后f()会从栈中返回一些垃圾。

我简单,天真,尝试失败。 例如:

 #include <stdio.h> union a_union { int i; double d; }; int f1(void) { union a_union t; td = 3333333.0; return ti; // gcc manual: 'type-punning is allowed, provided...' (C90 6.3.2.3) } int f2(void) { double d = 3333333.0; return ((union a_union *)&d)->i; // gcc manual: 'undefined behavior' } int main(void) { printf("%d\n", f1()); printf("%d\n", f2()); return 0; } 

工作正常,给CYGWIN:

 -2147483648 -2147483648 

看着汇编程序,我们看到gcc完全优化了tf1()只是存储预先计算的答案:

 movl $-2147483648, %eax 

f2()将3333333.0推到浮点栈上,然后提取返回值:

 flds LC0 # LC0: 1246458708 (= 3333333.0) (--> 80 bits) fstpl -8(%ebp) # save in d (64 bits) movl -8(%ebp), %eax # return value (32 bits) 

而且这些函数也是内联的(这似乎是一些微妙的严格消除错误的原因),但是在这里并不相关。 (这个汇编程序不是那么相关,但增加了确切的细节。)

另外请注意,采取地址显然是错误的(或正确的 ,如果你试图说明未定义的行为)。 例如,就像我们知道这是错误的:

 extern void foo(int *, double *); union a_union t; td = 3.0; foo(&t.i, &t.d); // undefined behavior 

我们也知道这是错误的:

 extern void foo(int *, double *); double d = 3.0; foo(&((union a_union *)&d)->i, &d); // undefined behavior 

有关这方面的背景讨论,请参阅:

jtc1/sc22/wg14/www/docs/n1422.pdf
http://gcc.gnu.org/ml/gcc/2010-01/msg00013.html
http://davmac.wordpress.com/2010/02/26/c99-revisited/
http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html
(= Google上的search页面然后查看caching页面)

什么是严格的锯齿规则?
C99中严格的锯齿规则(GCC)

在第一个环节,七个月前ISO会议的会议logging中,一位与会者在4.16节中指出:

有没有人认为规则足够清晰? 没有人真的能够解释它们。

其他说明:我的testing是用gcc 4.3.4,用-O2; 选项-O2和-O3意味着-strict-aliasing。 GCC手册中的示例假定sizeof(double) > = sizeof(int); 如果他们不平等没关系。

另外,如cellperformace链接中的Mike Acton所述, -Wstrict-aliasing=2 ,但不是 =3 ,会产生warning: dereferencing type-punned pointer might break strict-aliasing rules在此示例中, warning: dereferencing type-punned pointer might break strict-aliasing rules

GCC警告工会并不一定意味着工会目前没有工作。 但是这里有一个比你更简单的例子:

 #include <stdio.h> struct B { int i1; int i2; }; union A { struct B b; double d; }; int main() { double d = 3.0; #ifdef USE_UNION ((union A*)&d)->b.i2 += 0x80000000; #else ((int*)&d)[1] += 0x80000000; #endif printf("%g\n", d); } 

输出:

 $ gcc --version gcc (GCC) 4.3.4 20090804 (release) 1 Copyright (C) 2008 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ gcc -oalias alias.c -O1 -std=c99 && ./alias -3 $ gcc -oalias alias.c -O3 -std=c99 && ./alias 3 $ gcc -oalias alias.c -O1 -std=c99 -DUSE_UNION && ./alias -3 $ gcc -oalias alias.c -O3 -std=c99 -DUSE_UNION && ./alias -3 

所以在GCC 4.3.4上,工会“节省了一天”(假设我想输出“-3”)。 它禁用依赖于严格别名的优化,并在第二种情况下(仅)导致输出“3”。 使用-Wall,USE_UNION也会禁用types双关语言警告。

我没有gcc 4.4testing,但请给这个代码一个去。 你的代码实际上testing了d的内存是否在通过联合回读之前被初始化:我的testing是否被修改。

顺便说一句,安全的方式来读取一个整数的一半是一个整数:

 double d = 3; int i; memcpy(&i, &d, sizeof i); return i; 

通过GCC优化,结果如下:

  int thing() { 401130: 55 push %ebp 401131: 89 e5 mov %esp,%ebp 401133: 83 ec 10 sub $0x10,%esp double d = 3; 401136: d9 05 a8 20 40 00 flds 0x4020a8 40113c: dd 5d f0 fstpl -0x10(%ebp) int i; memcpy(&i, &d, sizeof i); 40113f: 8b 45 f0 mov -0x10(%ebp),%eax return i; } 401142: c9 leave 401143: c3 ret 

所以没有实际的memcpy调用。 如果你不这样做,你应该得到什么,如果工会演员在GCC停止工作;-)

编译器有两个不同的指向同一块内存的指针时发生混叠。 通过types化指针,你正在生成一个新的临时指针。 例如,如果优化程序重新sorting汇编指令,访问这两个指针可能会给出两个完全不同的结果 – 它可能会在写入同一个地址之前对一个读取进行重新sorting。 这就是为什么它是未定义的行为。

你不可能在非常简单的testing代码中看到这个问题,但是当有很多事情发生的时候它就会出现。

我认为这个警告就是要明确,工会不是一个特例,即使你可能期望它们是。

有关别名的更多信息,请参阅此维基百科文章: http : //en.wikipedia.org/wiki/Aliasing_(computing)#Conflicts_with_optimization

那么这是一个necro张贴,但这是一个恐怖的故事。 我正在移植一个程序,假定本地字节顺序是big endian。 现在我需要它在小端上工作了。 不幸的是,我不能在任何地方使用本地字节顺序,因为可以通过多种方式访问​​数据。 例如,一个64位整数可以被看作是两个32位整数或4个16位整数,或者甚至是16个4位整数。 更糟糕的是,没有办法确定存储在内存中的是什么,因为软件是某种字节码的解释器,数据是由该字节码形成的。 例如,字节码可能包含写入16位整数数组的指令,然后以32位浮点数访问其中的一对。 而且没有办法预测它或改变字节码。

因此,我不得不创build一组包装类来处理以大端顺序存储的值,而不pipe本地sorting如何。 在Visual Studio和Linux上的GCC上完美地工作,没有任何优化。 但是用gcc -O2,地狱爆发了。 经过很多debugging,我发现原因在这里:

 double D; float F; Ul *pF=(Ul*)&F; // Ul is unsigned long *pF=pop0->lu.r(); // r() returns Ul D=(double)F; 

此代码用于将存储在32位整数中的float的32位表示forms转换为doubleforms。 编译器似乎决定在赋值给D之后做* pF的赋值,结果是第一次执行代码的时候,D的值是垃圾,后来的值是“晚”了1次迭代。

奇迹般的是,那时没有其他问题。 因此,我决定继续在原始平台上testing我的新代码,在原生大端顺序的RISC处理器上testingHP-UX。 现在又重新开始了,这次是在我的新课堂上:

 typedef unsigned long long Ur; // 64-bit uint typedef unsigned char Uc; class BEDoubleRef { double *p; public: inline BEDoubleRef(double *p): p(p) {} inline operator double() { Uc *pu = reinterpret_cast<Uc*>(p); Ur n = (pu[7] & 0xFFULL) | ((pu[6] & 0xFFULL) << 8) | ((pu[5] & 0xFFULL) << 16) | ((pu[4] & 0xFFULL) << 24) | ((pu[3] & 0xFFULL) << 32) | ((pu[2] & 0xFFULL) << 40) | ((pu[1] & 0xFFULL) << 48) | ((pu[0] & 0xFFULL) << 56); return *reinterpret_cast<double*>(&n); } inline BEDoubleRef &operator=(const double &d) { Uc *pc = reinterpret_cast<Uc*>(p); const Ur *pu = reinterpret_cast<const Ur*>(&d); pc[0] = (*pu >> 56) & 0xFFu; pc[1] = (*pu >> 48) & 0xFFu; pc[2] = (*pu >> 40) & 0xFFu; pc[3] = (*pu >> 32) & 0xFFu; pc[4] = (*pu >> 24) & 0xFFu; pc[5] = (*pu >> 16) & 0xFFu; pc[6] = (*pu >> 8) & 0xFFu; pc[7] = *pu & 0xFFu; return *this; } inline BEDoubleRef &operator=(const BEDoubleRef &d) { *p = *dp; return *this; } }; 

对于一些非常奇怪的原因,第一个赋值操作符只能正确分配1到7字节。字节0总是有一些废话,因为有一个符号位和一部分顺序,这一切都被打破了。

我试图使用工会作为解决方法:

 union { double d; Uc c[8]; } un; Uc *pc = un.c; const Ur *pu = reinterpret_cast<const Ur*>(&d); pc[0] = (*pu >> 56) & 0xFFu; pc[1] = (*pu >> 48) & 0xFFu; pc[2] = (*pu >> 40) & 0xFFu; pc[3] = (*pu >> 32) & 0xFFu; pc[4] = (*pu >> 24) & 0xFFu; pc[5] = (*pu >> 16) & 0xFFu; pc[6] = (*pu >> 8) & 0xFFu; pc[7] = *pu & 0xFFu; *p = un.d; 

但它也没有工作。 事实上,这个数字好一点 – 只是负数而已。

在这一点上,我正在考虑为本地sorting添加一个简单的testing,然后通过char*指针与if (LITTLE_ENDIAN)检查周围的事情。 更糟糕的是,这个程序大量使用了各地的工会,现在看来工作还不错,但是如果这个混乱事件突然间没有明显的原因,我不会感到惊讶。

你见过这个吗 ? 什么是严格的锯齿规则?

该链接包含一个本文链接到gcc的例子。 http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html

尝试这样的联合会更接近这个问题。

 union a_union { int i; double *d; }; 

这样你有2种types,一个int和一个double指向相同的内存。 在这种情况下使用double (*(double*)&i)可能会导致问题。

你断言下面的代码是“错误的”:

 extern void foo(int *, double *); union a_union t; td = 3.0; foo(&t.i, &t.d); // undefined behavior 

… 是错的。 只要把两个工会成员的地址交给一个外部函数,不会导致不确定的行为; 你只能通过以无效方式解引用其中的一个指针来获得。 例如,如果函数foo立即返回而不取消引用你传递的指针,那么行为不是未定义的。 在严格阅读C99标准的情况下,甚至有些情况下指针可以被解除引用而不会调用未定义的行为; 例如,它可以读取第二个指针引用的值,然后通过第一个指针存储一个值,只要它们都指向一个dynamic分配的对象(即没有“声明types”的对象)。

我真的不明白你的问题。 编译器完成了你的例子中应该做的事情。 union转换是你在f1所做的。 在f2它是一个普通的指针types转换,你把它转换成一个联合是不相关的,它仍然是一个指针转换

这是我的:认为这是所有GCC v5.x和更高版本中的一个错误

 #include <iostream> #include <complex> #include <pmmintrin.h> template <class Scalar_type, class Vector_type> class simd { public: typedef Vector_type vector_type; typedef Scalar_type scalar_type; typedef union conv_t_union { Vector_type v; Scalar_type s[sizeof(Vector_type) / sizeof(Scalar_type)]; conv_t_union(){}; } conv_t; static inline constexpr int Nsimd(void) { return sizeof(Vector_type) / sizeof(Scalar_type); } Vector_type v; template <class functor> friend inline simd SimdApply(const functor &func, const simd &v) { simd ret; simd::conv_t conv; conv.v = vv; for (int i = 0; i < simd::Nsimd(); i++) { conv.s[i] = func(conv.s[i]); } ret.v = conv.v; return ret; } }; template <class scalar> struct RealFunctor { scalar operator()(const scalar &a) const { return std::real(a); } }; template <class S, class V> inline simd<S, V> real(const simd<S, V> &r) { return SimdApply(RealFunctor<S>(), r); } typedef simd<std::complex<double>, __m128d> vcomplexd; int main(int argc, char **argv) { vcomplexd a,b; av=_mm_set_pd(2.0,1.0); b = real(a); vcomplexd::conv_t conv; conv.v = bv; for(int i=0;i<vcomplexd::Nsimd();i++){ std::cout << conv.s[i]<<" "; } std::cout << std::endl; } 

应该给

 c010200:~ peterboyle$ g++-mp-5 Gcc-test.cc -std=c++11 c010200:~ peterboyle$ ./a.out (1,0) 

但在-O3下:我认为这是错误的和编译器错误

 c010200:~ peterboyle$ g++-mp-5 Gcc-test.cc -std=c++11 -O3 c010200:~ peterboyle$ ./a.out (0,0) 

在g ++ 4.9下

 c010200:~ peterboyle$ g++-4.9 Gcc-test.cc -std=c++11 -O3 c010200:~ peterboyle$ ./a.out (1,0) 

在llvm xcode下

 c010200:~ peterboyle$ g++ Gcc-test.cc -std=c++11 -O3 c010200:~ peterboyle$ ./a.out (1,0)