clang++ 的选项 -fno-strict-enums 有啥作用?
Posted
技术标签:
【中文标题】clang++ 的选项 -fno-strict-enums 有啥作用?【英文标题】:What does clang++'s option -fno-strict-enums do?clang++ 的选项 -fno-strict-enums 有什么作用? 【发布时间】:2020-11-07 14:40:39 【问题描述】:两个月前,我作为clang++
错误报告了以下C++ 程序在使用clang++ -O2 -fno-strict-enums
编译时将z
设置为4294967295
。
enum e e1, e2 e;
long long x, y, z;
char *p;
void f(void)
e = (enum e) 4294967295;
x = (long long) e;
y = e > e1;
z = &p[e] - p;
由于程序未定义,我的错误报告因无效而被关闭。我的感觉是使用选项-fno-strict-enums
可以定义它。
据我所知,Clang 没有名副其实的文档,因为它的目标是在接受的选项及其含义方面与 GCC 兼容。我阅读了 GCC 的选项 -fno-strict-enums
的文档,说程序应该将 z
的值设置为 -1
:
-fstrict-枚举
允许编译器使用枚举类型的值只能是枚举值之一的假设进行优化 (在 C++ 标准中定义;基本上,一个值可以是 以表示所有数据所需的最小位数表示 枚举器)。如果程序使用 cast 将任意整数值转换为枚举类型。
请注意,仅记录了选项-fstrict-enums
,但似乎很清楚-fno-strict-enums
禁用了-fstrict-enums
启用的编译器行为。我无法针对 GCC 的文档提交错误,因为生成一个将 z
设置为 -1
的二进制文件,我理解 -fno-strict-enums
的要求,正是 g++ -O2 -fno-strict-enums
所做的。
谁能告诉我-fno-strict-enums
在 Clang 中的作用(以及在 GCC 中,如果我误解了它在 GCC 中的作用),以及该选项的值在 Clang 中的任何地方是否有任何影响?
作为参考,我的错误报告是here,显示我的意思的编译器资源管理器链接是here。用作参考的版本是针对 I32LP64 架构的 Clang 10.0.1 和 GCC 10.2。
【问题讨论】:
@JaMiT 对不起,哪个未初始化的变量?至于第二个潜在的UB,转换中的溢出是实现定义的,所以在(enum e) 4294967295
中,我希望在将4294967295
转换为enum e
的基础类型时应用实现定义的行为。实现定义的行为是扭曲的。
@JaMiT 没有显式初始化器的命名空间范围内的变量(例如全局变量)将被“零”初始化。
@JaMiT 编译器必须为f
生成代码,该代码对于p
的所有初始值都正确运行,从而定义了f
的源代码。如果您坚持要查看f
的调用上下文不会使问题变得毫无意义,则可以是p = malloc(5000000000U); if (!p) abort(); f();
@JaMiT 如果你有一个论据解释函数 f
对于所有可能的调用上下文都是未定义的,那也可以回答我的问题。但我不认为“p 必须指向一个包含 4294967294 个元素的数组”是那个论点,因为p
可以做到这一点,并且当p
这样做时,编译器生成的代码必须正确运行。
-fno-strict-enums
禁用基于枚举值范围的严格定义的优化。但是在枚举的有效范围内违反 ISO 14882 仍然是未定义的行为。
【参考方案1】:
-fno-strict-enums
的作用是取消-fstrict-enums
。也就是说,不允许编译器使用枚举类型的值只能是枚举值之一的假设进行优化。我想强调一下,选择这个词是“允许的”,而不是“必需的”。可能很难看到不再允许最初未完成的事情的影响。不过,我想我已经找到了一个可以看到这一点的例子。
首先,我想在问题的上下文中澄清“枚举的值”。枚举e
有两个枚举器,其值为0
和1
。表示这些值所需的最小位数是 1。因此,枚举的值都是可以用 1 位表示的值。在这种情况下,这恰好与枚举器的值一致,但在其他示例中不能保证。
接下来,让我们从问题代码中删除一行。
enum e e1, e2 e;
long long x, y, z;
char *p;
void f(void)
//e = (enum e) 4294967295;
x = (long long) e;
y = e > e1;
z = &p[e] - p;
我删除的行干扰了strict-enum
标志。当编译器确切知道e
的值是什么 时,该标志允许编译器做出不必要的假设。编译器可以合理地选择不假设e
只能保存0
或1
,但很明显它只是被赋予了不同的值。 (这种干扰并不取决于 4294967295
对于 32 位有符号整数而言太大,而仅取决于 4294967295
是编译时值。作为另一个示例,将 (enum e) 2
分配给 e
会也会造成这种干扰。)
专注于分配y = e > e1
。如果-fno-strict-enums
生效,则唯一可用的优化是将e1
替换为0
。但是,如果我们可以假设 e
只能是 0
或 1
(枚举的值,恰好也是枚举数的值),则可以进行另一种优化。
如果e
是0
,则以下具有相同的值:
(long long) (e > e1)
(long long) (0 > 0)
(long long) false
(long long) e
如果e
是1
,则以下具有相同的值:
(long long) (e > e1)
(long long) (1 > 0)
(long long) true
(long long) e
在任何一种情况下,我们都可以跳过比较并简单地将e
转换为long long
。这反映在 clang 10 为行 y = e > e1
生成的程序集中。
与-fstrict-enums
movq %rax, y(%rip)
与-fno-strict-enums
xorl %ecx, %ecx
testl %eax, %eax
setg %cl
movq %rcx, y(%rip)
已对 -fstrict-enums
进行了优化,而 -fno-strict-enums
不允许这样做。
【讨论】:
我理解您的回答是“-fno-strict-enums 不允许执行(enum e) 4294967295
的程序,它只会禁用依赖于程序不执行此操作的 [one] 优化。如果这样做,程序仍然无效”,这可能是对 Clang 中发生的事情的准确描述,但这不是 GCC 文档的工作方式,例如,-fno-strict-aliasing
。如果您选择该选项:再次,仅记录 -fstrict-aliasing,文档再次开始“允许编译器假定适用的最严格的别名规则......”,但这意味着......
……至少在 GCC 的情况下,违反严格别名但没有其他规则的程序应该根据程序员的意图进行翻译。如果仅禁用基于严格别名而不是其他优化的优化,该选项将完全无法使用。
@PascalCuoq 不,这看起来不像我的答案。我没有写什么程序是允许的。原因之一是-fno-strict-enums
和fstrict-enums
(也不是fno-strict-aliasing
或任何其他控制允许优化的标志)都不会导致无效程序变为有效。优化可能会导致无效程序按预期运行,但这与未定义行为按预期运行一样是运气问题。
@PascalCuoq 您之前坚持认为您的问题是关于 -fno-strict-enums
的作用。不是为什么您的示例无效,而是-fno-strict-enums
做了什么(我通过放弃讨论您的代码无效或无效的原因来遵守)。请尝试以开放的心态来回答问题,放弃你先入为主的想法,即它以某种方式与程序的有效性有关。
我可以向您保证,-fno-strict-aliasing
的辅助解释(对于该选项有很多,与 -fno-strict-enums
不同)将其描述为更改编译器接受的方言的选项,使事情变得通常是 UB 定义的(允许编译器这样做)。 GCC 的开发人员当然已经看到了这些二手资料,并且如果不是人们应该如何理解和使用-fno-strict-aliasing
,他们将有足够的时间来纠正误解。除此之外,我以开放的心态接近答案,我渴望理解。以上是关于clang++ 的选项 -fno-strict-enums 有啥作用?的主要内容,如果未能解决你的问题,请参考以下文章