对于赏金:如何在不禁用或降低优化级别的情况下根据具体情况禁用此行为?
以下条件表达式是在 MinGW GCC 3.4.5 上编译的,其中
a
是 signed long
类型,m
是 unsigned long
类型。
if (!a && m > 0x002 && m < 0x111)
使用的
CFLAGS
是-g -O2
。这是相应的程序集 GCC 输出(用 objdump
转储)
120: 8b 5d d0 mov ebx,DWORD PTR [ebp-0x30]
123: 85 db test ebx,ebx
125: 0f 94 c0 sete al
128: 31 d2 xor edx,edx
12a: 83 7d d4 02 cmp DWORD PTR [ebp-0x2c],0x2
12e: 0f 97 c2 seta dl
131: 85 c2 test edx,eax
133: 0f 84 1e 01 00 00 je 257 <_MyFunction+0x227>
139: 81 7d d4 10 01 00 00 cmp DWORD PTR [ebp-0x2c],0x110
140: 0f 87 11 01 00 00 ja 257 <_MyFunction+0x227>
120
-131
可以很容易地追踪为首先评估 !a
,然后评估 m > 0x002
。第一个跳转条件直到 133
才会发生。此时,无论第一个表达式的结果如何:!a
,都已计算了两个表达式。如果 a
等于 0,则表达式可以(并且应该)立即得出结论,但这里没有这样做。
这与 C 标准有何关系? C 标准要求布尔运算符在确定结果后立即短路?
C 标准仅规定了“抽象机”的行为;它没有指定程序集的生成。只要程序的可观察行为与抽象机上的行为相匹配,实现就可以使用它喜欢的任何物理机制来实现语言构造。标准 (C99) 中的相关部分是 5.1.2.3 程序执行。
这可能是编译器优化,因为比较整数类型没有副作用。您可以尝试在不进行优化的情况下进行编译,或者使用有副作用的函数而不是比较运算符,看看它是否仍然会这样做。
例如,尝试一下
if (printf("a") || printf("b")) {
printf("c\n");
}
它应该打印
ac
正如其他人提到的,此汇编输出是编译器优化,不会影响程序执行(据编译器所知)。 如果您想选择性地禁用此优化,则需要告诉编译器您的变量不应跨代码中的序列点进行优化。
序列点是控制表达式(
if
、switch
、while
、do
以及for
的所有三个部分中的求值)、逻辑OR和AND、条件语句(?:
) 、逗号和return
声明。
为了防止编译器在这些点上进行优化,您必须声明变量
volatile
。 在您的示例中,您可以指定
volatile long a;
unsigned long m;
{...}
if (!a && m > 0x002 && m < 0x111) {...}
这样做的原因是
volatile
用于指示编译器它不能预测等效机器相对于变量的行为。 因此,它必须严格遵守代码中的序列点。
编译器的优化 - 它将结果放入 EBX,将其移至 AL(EAX 的一部分),对 EDX 进行第二次检查,然后根据 EAX 和 EDX 的比较进行分支。 这可以节省一个分支并使代码运行得更快,而不会产生任何副作用。
如果您使用
-O0
而不是 -O2
进行编译,我想它会产生更简单的汇编,更符合您的期望。
无论哪种方式,代码都表现正确(即符合语言标准的要求)。
您似乎正在尝试找到一种生成特定汇编代码的方法。 在两种可能的汇编代码序列中,两者的行为方式相同,您会发现一种令人满意,另一种则不满意。
保证令人满意的汇编代码序列的唯一真正可靠的方法是显式编写汇编代码。 gcc 确实支持内联汇编。
C 代码指定行为。 汇编代码指定机器代码。
但这一切都提出了一个问题:为什么这对你很重要? (我不是说不应该,我只是不明白为什么应该。)
编辑:
a
和 m
到底是如何定义的? 如果,正如您所建议的,它们与内存映射设备相关,那么它们应该被声明为 volatile
- 这可能正是您问题的解决方案。 如果它们只是普通变量,那么编译器可以对它们做任何它喜欢的事情(只要它不影响程序的可见行为)因为你没有要求它不这样做。