当询问C中常见的未定义行为时,灵魂比我提到的严格别名规则更加开明.
他们在说什么?
我们最近在大学里开了一个关于多种语言编程特色的讲座.
讲师写下了以下功能:
inline u64 Swap_64(u64 x)
{
u64 tmp;
(*(u32*)&tmp) = Swap_32(*(((u32*)&x)+1));
(*(((u32*)&tmp)+1)) = Swap_32(*(u32*) &x);
return tmp;
}
Run Code Online (Sandbox Code Playgroud)
虽然我完全理解这在可读性方面也是非常差的风格,但他的主要观点是这部分代码在生产代码中运行良好,直到它们实现了高优化级别.然后,代码将什么都不做.
他说,变量的所有赋值tmp都将由编译器优化.但为什么会这样呢?
我知道有些情况下变量需要声明为volatile,这样编译器就不会触及它们,即使他认为它们永远不会被读或写,但我不知道为什么会发生这种情况.
我正在阅读ISO/IEC 9899:TC2中第6.5段的第7段.
它通过以下方式宽恕对对象的左值访问:
一种聚合或联合类型,包括其成员中的上述类型之一(包括递归地,子聚合或包含联合的成员),
请参阅文档,了解"前面提到的"类型,但它们肯定包含对象的有效类型.
它的部分标注为:
此列表的目的是指定对象可能或可能没有别名的情况.
我读这是说(例如)以下定义很好:
#include <stdlib.h>
#include <stdio.h>
typedef struct {
unsigned int x;
} s;
int main(void){
unsigned int array[3] = {73,74,75};
s* sp=(s*)&array;
sp->x=80;
printf("%d\n",array[0]);
return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)
该程序应输出80.
我不是在提倡这是一个好的(或非常有用的)想法,并且承认我在某种程度上解释它是因为我无法想到其他意味着什么并且不能相信它是一个毫无意义的句子!
也就是说,我看不出有理由禁止它.我们所知道的是该位置的对齐和内存内容是否兼容,sp->x为什么不呢?
它似乎甚至可以说,如果我double y;在结构的末尾添加(说)a ,我仍然可以array[0]通过sp->x这种方式访问它.
但是,即使数组大于sizeof(s)任何访问尝试sp->y都是'所有下注'未定义的行为.
可能我礼貌地要求人们说出那句话宽恕而不是进入一个扁平的旋转喊"严格混淆UB严格别名UB"似乎经常是这些事情的方式.
我已经阅读了许多有关此警告的问题(取消引用类型双关指针会破坏严格别名规则,取消引用类型双关指针会破坏严格别名规则 [-Wstrict-aliasing],严格别名规则是什么?,“取消引用类型双关指针将破坏严格的别名规则”警告和其他人)并且对我的警告完全感到困惑。
所以我有一个结构:
typedef struct {
unsigned char precision;
unsigned char scale;
unsigned char array[33];
} DBNUMERIC;
Run Code Online (Sandbox Code Playgroud)
当从 MS SQL Server 检索数据时,该结构由 FreeTDS 库填充。我知道从array[1]那里开始是 64 位整数(大端),我想得到它。我使用以下代码:
int64_t result = 0;
result = be64toh(*((decltype(result)*)(numeric.array + 1)));
Run Code Online (Sandbox Code Playgroud)
但是 GCC 给了我警告dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]。但是如果我使用代码:
int64_t result = 0;
decltype(result)* temp_ptr = (decltype(result)*)(numeric.array + 1);
decltype(result) temp = *temp_ptr;
result = be64toh(temp);
Run Code Online (Sandbox Code Playgroud)
没有关于违反严格别名规则的警告。我不认为此代码与原始代码不同,因此我很困惑。如何将数组中的 8 个字节转换为int64_t变量?
在C中,有符号整数和无符号整数在内存中的存储方式不同.当类型在运行时清除时,C还隐式转换有符号整数和无符号整数.但是,当我尝试以下代码段时,
#include <stdio.h>
int main() {
unsigned int a = 5;
signed int b = a;
signed int c = *(unsigned int*)&a;
signed int d = *(signed int*)&a;
printf("%u\n", a);
printf("%i\n", b);
printf("%i\n", c);
printf("%i\n", d);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
预期产量:
5
5 //Implicit conversion occurs
5 //Implicit conversion occurs, because it knows that *(unsigned int*)&a is an unsigned int
[some crazy number] //a is casted directly to signed int without conversion
Run Code Online (Sandbox Code Playgroud)
然而,实际上,它输出
5
5
5
5
Run Code Online (Sandbox Code Playgroud)
为什么?
c ×4
c++ ×3
arrays ×1
endianness ×1
gcc ×1
gcc-warning ×1
optimization ×1
struct ×1
type-punning ×1