警告的后果“;取消引用类型双关指针将打破严格的别名规则”;

警告的后果“;取消引用类型双关指针将打破严格的别名规则”;,c,casting,compiler-optimization,strict-aliasing,aliasing,C,Casting,Compiler Optimization,Strict Aliasing,Aliasing,我对类似的主题和一些相关的材料进行了一些查询。 但我的查询主要是为了理解下面代码的警告。我不需要修理!! 我知道有两种方法,一种是联合,另一种是使用memcpy uint32 localval; void * DataPtr; localval = something; (*(float32*)(DataPtr))= (*(const float32*)((const void*)(&localval))); 请注意以下要点 1.这里的强制转换涉及的两种类型都是32位的。(还是我错了?

我对类似的主题和一些相关的材料进行了一些查询。 但我的查询主要是为了理解下面代码的警告。我不需要修理!! 我知道有两种方法,一种是联合,另一种是使用memcpy

uint32 localval;
void * DataPtr;
localval = something;
(*(float32*)(DataPtr))= (*(const float32*)((const void*)(&localval)));
请注意以下要点
1.这里的强制转换涉及的两种类型都是32位的。(还是我错了?
2.两者都是局部变量

编译器特定点:
1.代码应该是独立于平台的,这是一个要求
2.我在GCC上编译,它就像预期的那样工作。(我可以将int重新解释为float),这就是我忽略警告的原因

我的问题
1.在这种别名情况下,编译器可以执行哪些优化?
2.由于两者都会占用相同的大小(如果没有请更正),这样的编译器优化会有什么副作用?
3.我可以安全地忽略警告或关闭别名吗?
4.如果编译器没有执行优化,并且我的程序在第一次编译后没有中断?我是否可以安全地假设每次编译器都会以相同的方式运行(不进行优化)?
5.别名是否也适用于void*typecast?还是仅适用于标准类型转换(int、float等) 6.禁用别名规则会产生什么影响

已编辑
1.基于R和Matt McNabb的修正

2.添加了一个新的问题

常量没有任何区别。要检查类型的大小是否相同,可以将
sizeof(uint32)
sizeof(float32)
进行比较。这两种类型也可能有不同的对齐要求

把这些事情放在一边;该行为未定义为读取
localval
的内存,就像它存储了一个浮点一样,这就是严格的别名规则所说的

6.5#6:

用于访问其存储值的对象的有效类型是 对象,如果有的话

6.5#7:

对象的存储值只能由左值表达式访问,该左值表达式具有 以下类型

localval
具有有效类型
uint32
,并且“以下类型”列表不包括
float32
,因此这违反了别名规则

如果您在动态分配的内存中使用别名,那么情况就不同了。没有“声明类型”,因此“有效类型”是对象中最后存储的类型。您可以
malloc(sizeof(uint32))
,然后在其中存储一个float32并读回

总而言之,您似乎在问“我知道这是未定义的,但是我可以依靠我的编译器成功地完成它吗?”要回答这个问题,您必须指定编译器是什么,以及至少使用什么开关调用它


当然,也可以选择调整代码,使其不违反严格的别名规则,但您没有提供足够的背景信息来继续此过程。

您有一个不完整的示例(如所述,它显示UB,因为
localval
未初始化),因此让我完成它:

uint32 localval;
void * DataPtr;
DataPtr = something;
localval = 42;
(*(float32*)(DataPtr))= (*(const float32*)((const void*)(&localval)));
现在,由于
localval
具有类型
uint32
*(const float32*)((const void*)(&localval))
具有类型
float32
,因此它们不能别名,因此编译器可以自由地重新排序最后两个语句。这显然会导致与您想要的行为不同的行为

正确的书写方法是:

memcpy(DataPtr, &localval, sizeof localval);

语言标准试图在使用该语言的程序员和希望使用一系列优化来生成速度相当快的代码的编译器编写者之间取得平衡。将变量保存在寄存器中就是这样一种优化。对于程序某个部分中“活动”的变量,编译器尝试在寄存器中分配它们。存储在指针的地址可以存储在程序地址空间的任何位置,这将使寄存器中的每个变量无效。有时编译器可以分析程序并找出指针可以或不能指向的位置,但是C(和C++)语言标准认为这是一个不必要的负担,而对于“系统”类型的程序来说,通常是不可能完成的任务。因此,语言标准通过指定某些构造导致“未定义的行为”来放松约束,因此编译器编写者可以假设它们不会发生,并在该假设下生成更好的代码。在
严格别名的情况下
达成的妥协是,如果使用一种指针类型存储到内存,则假定不同类型的变量保持不变,因此可以保留在寄存器中,或者可以相对于指针存储对这些其他类型的存储和加载进行重新排序

本文“未定义的行为:我的代码怎么了?”中有许多此类优化的例子

这里有一个Linux内核中违反严格别名规则的例子,显然内核通过告诉编译器不要使用严格别名规则进行优化来避免问题,“Linux内核使用-fno严格别名进行优化” 禁用基于严格别名的优化。”

扩展代码首先将8写入iwe->len,这是 键入uint16_t,然后读取指向同一位置的iwe iwe->len的内存位置,使用不同类型的int。根据严格的别名规则,GCC得出结论,读取 并且写入不会发生在同一个内存位置, 因为它们使用不同的指针类型,并对这两种指针重新排序 操作。因此,生成的代码复制了一个过时的iwe->len 价值Linux内核使用
-fno-strict-
struct iw_event {
    uint16_t len; /* Real length of this stuff */
    ...
};
static inline char * iwe_stream_add_event(
    char * stream, /* Stream of events */
    char * ends, /* End of stream */
    struct iw_event *iwe, /* Payload */
    int event_len ) /* Size of payload */
{
    /* Check if it's possible */
    if (likely((stream + event_len) < ends)) {
        iwe->len = event_len;
        memcpy(stream, (char *) iwe, event_len);
        stream += event_len;
    }
    return stream;
}
iwe->len = 8;
*(int *)stream = *(int *)((char *)iwe);
*((int *)stream + 1) = *((int *)((char *)iwe) + 1);