当前位置: 首页 > 知识库问答 >
问题:

警告“取消引用类型双关指针将违反严格混叠规则”的连续序列

甄文彬
2023-03-14

我已经浏览了一些关于类似主题的查询和一些与之相关的材料。但我的查询主要是为了理解下面代码的警告。我不想要修复!!我知道有两种方法,联合或使用memcpy。

uint32 localval;
void * DataPtr;
localval = something;
(*(float32*)(DataPtr))= (*(const float32*)((const void*)(&localval)));

请注意以下要点
1。这里涉及的两种类型都是32位。(还是我错了?)
2。两者都是局部变量。

编译器特定点:<br>1.代码应该与平台无关,这是一个要求
2.我在GCC上编译,它就像预期的那样工作。(我可以将int重新解释为float),这就是为什么我忽略了警告。

我的问题
1.在这种混叠的情况下,编译器可以执行哪些优化?
2.由于两者都将占据相同的大小(如果不是,请纠正我),因此编译器优化的副作用是什么?
3. 我可以安全地忽略警告或关闭混叠吗?
4. 如果编译器没有执行优化,并且我的程序在我第一次编译后没有坏?我可以html" target="_blank">安全地假设每次编译器都会以相同的方式(不进行优化)吗?
5. 混叠是否也适用于 void * typecast ? 或者它只适用于标准类型转换(int,float 等)?
6. 如果禁用混叠规则会有什么影响?

已编辑< br> 1。基于R's和Matt McNabb的更正< br> 2。添加了一个新问题

共有3个答案

穆德海
2023-03-14

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

把这些放在一边;该行为未定义为读取<code>localval

6.5#6:

对象对其存储值的访问的有效类型是对象的声明类型,如果有的话。

6.5#7:

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

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

如果您在动态分配的内存中出现混淆现象,那就不同了。没有“声明类型”,所以“有效类型”是最后存储在对象中的任何类型。您可以malloc(sizeof(uint32)),然后在其中存储一个flat32并读取它。

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

当然,也有调整代码的选项,这样它就不会违反严格的混淆现象规则,但是你还没有提供足够的背景信息来继续这个轨道。

赫连骏
2023-03-14

您有一个不完整的示例(如所写,它显示UB,因为<code>localval</code>未初始化),所以让我来完成它:

uint32 localval;
void * DataPtr;
DataPtr = something;
localval = 42;
(*(float32*)(DataPtr))= (*(const float32*)((const void*)(&localval)));

现在,由于< code>localval具有类型< code>uint32和< code > *(const float 32 *)((const void *)(

正确的写法是:

memcpy(DataPtr, &localval, sizeof localval);
越鸿才
2023-03-14

语言标准试图在将使用该语言的程序员和希望使用一系列优化来生成合理快速代码的编译器编写者之间的利益冲突中取得平衡。将变量保存在寄存器中就是这样一种优化。对于在程序的某个部分中“活动”的变量,编译器尝试在寄存器中分配它们。在指针中的地址处存储可以存储程序地址空间中的任何位置-这将使寄存器中的每个变量无效。有时编译器可以分析程序并找出指针可以指向或不可以指向的位置,但C(和C)语言标准认为这是一个不适当的负担,对于“系统”类型的程序来说,这通常是一项不可能的任务。因此,语言标准通过指定某些构造导致“未定义的行为”来放松约束,这样编译器编写者可以假设它们不会发生,并在该假设下生成更好的代码。在<code>严格别名

在这篇论文“未定义的行为:我的代码发生了什么?”中有许多此类优化的示例。

http://pdos.csail.mit.edu/papers/ub:apsys12.pdf

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

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;
}

图7:include/net/iw_handler中的严格别名冲突。h,它使用GCC的<code>-fno严格别名

2.6类型-穿孔指针偏移

C为程序员提供了将一种类型的指针转换为另一种类型的自由。指针转换经常被滥用来重新解释具有不同类型的给定对象,这种技巧称为类型双关。通过这样做,程序员期望两个不同类型的指针指向相同的内存位置(即混淆现象)。然而,C标准对混淆现象有严格的规则。特别是,除了少数例外,两个不同类型的指针不别名[19,6.5]。违反严格的混淆现象会导致未定义的行为。图7显示了Linux内核的一个示例。函数第一次更新iwe-

iwe->len = 8;
*(int *)stream = *(int *)((char *)iwe);
*((int *)stream + 1) = *((int *)((char *)iwe) + 1);

扩展代码首先写入8到iwe-

答案

1) 在这种别名情况下,编译器可以执行哪些优化?

语言标准对于严格符合标准的程序的语义(行为)是非常具体的——编译器作者或语言实现者有责任使其正确。一旦程序员越界并调用未定义的行为,那么标准很清楚,证明这将按预期工作的举证责任落在程序员身上,而不是编译器作者身上——在这种情况下,编译器已经很好地警告未定义的行为已被调用,尽管它甚至没有义务这样做。有时,令人恼火的是,人们会告诉你,在这一点上“任何事情都可能发生”,通常是一些笑话/夸张。在您的程序中,编译器可以生成“典型的平台”代码,并将< code>something的值存储到< code>localval中,然后从< code>localval加载并存储到< code>DataPtr中,如您所愿,但请理解这不是必须的。它将< code>localval的存储视为< code>uint32类型的存储,并从< code >(*(const float 32 *)((const void *)(

2) 由于两者占用相同的大小(如果不是,请更正),这样的编译器优化会有什么副作用?

效果可能是未定义的值存储在DataPtr指向的地址处。

3)我可以安全地忽略警告或关闭别名吗?

这是特定于您正在使用的编译器的——如果编译器记录了一种关闭严格别名优化的方法,那么是的,不管编译器做出什么警告。

4) 如果编译器没有执行优化,并且我的程序在第一次编译后没有中断?我是否可以安全地假设每次编译器都会以相同的方式运行(不进行优化)?

也许,有时程序另一部分的非常小的更改可能会改变编译器对此代码执行的操作,请想一想,如果函数是“内联的”,则可能会被扔进代码的其他部分的混合中,请参阅此SO问题。

5) 别名是否也适用于void*类型转换?还是只适用于标准类型转换(int、float等)?

您不能取消引用<code>void*</code>所以编译器只关心最终转换的类型(在C语言中,如果您将<code>常量</code>转换为<code>非常量</code>或反之亦然,它会发牢骚)。

6)如果我禁用别名规则,会有什么影响?

请参阅您的编译器文档-一般来说,如果您这样做(就像上面文章中的示例中选择的Linux内核那样),您将得到较慢的代码,然后将其限制在一个小的编译单元中,只使用必要的函数。

结论

我知道你的问题是出于好奇,并试图更好地理解这是如何工作的(或可能不工作)。您提到要求代码是可移植的,这意味着要求程序兼容并且不调用未定义的行为(请记住,如果您这样做,负担就在您身上)。在这种情况下,正如您在问题中指出的那样,一种解决方案是使用memcpy,因为事实证明这不仅使您的代码兼容并因此可移植,它还可以在当前gcc上以最有效的方式实现您的意图优化级别-O3编译器将memcpy转换为单个指令,将localval的值存储在DataPtr指向的地址,请参阅此处的coliru-查找movl%esi,(%rdi)指令。

 类似资料:
  • 当违反严格的混淆现象规则时,我试图掌握未定义的行为。为了理解它,我读了很多关于SO的文章。然而,还有一个问题:我并不真正理解两种类型的非法别名。cpp-参考指出: 类型混淆现象 每当尝试通过AliasedType类型的glvalue读取或修改DynamicType类型对象的存储值时,除非以下之一为真,否则行为未定义: AliasedType和DynamicType类似 AliasedType是Dy

  • 问题内容: 我是GoLang的新手,来自Delphi C ++世界-诚然对这种语言感到非常兴奋,我认为它将成为“下一件大事”。 我正在尝试了解Go解析器和编译器如何处理指针和引用-似乎找不到任何放置一些明确规则的地方。 例如,在下面的代码示例中,返回类型和局部变量是指针类型,并且在其声明中需要使用指针符号,但是在使用时不必取消引用它们:。但是在同一代码中,输入参数被声明为指针,并且必须被取消引用才

  • 我不知道为什么下面的代码运行得很好,没有< code>gcc错误(< code >-f strict-aliasing-Wstrict-aliasing = 1 )。 如果我遵循严格的别名规则: n1570,§6.5表达式 对象的存储值只能由具有以下类型之一的左值表达式访问: -与对象的有效类型兼容的类型, — 与对象的有效类型兼容的类型的限定版本, -与对象的有效类型对应的有符号或无符号类型的类

  • 在这些注释中,user @Deduplicator坚持认为,如果别名指针或别名指针是指向字符的指针类型(限定或非限定,有符号或无符号< code>char *),则严格的别名规则允许通过不兼容的类型进行访问。所以,他的观点基本上是 和 符合并具有定义的行为。 然而,在我看来,只有第一种形式是有效的,即当别名指针是指向char的指针时;然而,在另一个方向上却不能,即当别名指针指向不兼容的类型(而不是

  • 注意:学习严格的别名规则。请耐心等待。 代码示例(t935.c): 调用: 问题:向函数传递指向同一联合成员的两个指针是否违反了严格的别名规则? 这个问题源于工会和类型双关语。 更新20210901 如果union类型在全局范围内定义,会发生什么? 对于“联合在全局范围内定义”(实际上是文件范围)的情况,gcc和clang都显示出与上面报告的gcc相同的结果。

  • 我正在阅读关于reinterpret_cast的笔记,它是混淆现象(http://en.cppreference.com/w/cpp/language/reinterpret_cast)。 我写了代码: 我认为这些规则在这里不适用: T2是对象的(可能是cv限定的)动态类型 T2和T1都是指向相同类型T3的指针(可能是多级的,可能在每个级别都是cv限定的)(因为C11) T2是一个聚合类型或并集类