C 对布尔操作数短路而无副作用

C 对布尔操作数短路而无副作用,c,gcc,compiler-construction,mingw,compiler-optimization,C,Gcc,Compiler Construction,Mingw,Compiler Optimization,对于赏金:如何在不禁用或降低优化级别的情况下逐个禁用此行为? 以下条件表达式是在MinGW GCC 3.4.5上编译的,其中a为signed long类型,m为unsigned long类型 if (!a && m > 0x002 && m < 0x111) 120-131可以很容易地追溯到第一次评估!a,然后评估m>0x002。第一个跳转条件直到133才出现。此时,无论第一个表达式的结果如何,已计算了两个表达式:!a。如果a等于零,则表达式可以(并

对于赏金:如何在不禁用或降低优化级别的情况下逐个禁用此行为?

以下条件表达式是在MinGW GCC 3.4.5上编译的,其中
a
signed long
类型,
m
unsigned long
类型

if (!a && m > 0x002 && m < 0x111)
120
-
131
可以很容易地追溯到第一次评估
!a
,然后评估
m>0x002
。第一个跳转条件直到
133
才出现。此时,无论第一个表达式的结果如何,已计算了两个表达式:
!a
。如果
a
等于零,则表达式可以(并且应该)立即得出结论,此处不做此操作


这与C标准有什么关系?C标准要求布尔运算符在确定结果后立即短路?

这可能是编译器优化,因为比较整数类型没有副作用。您可以尝试在不进行优化的情况下进行编译,或者使用具有副作用的函数而不是比较运算符,然后查看它是否仍然具有此功能

例如,试试看

if (printf("a") || printf("b")) {
    printf("c\n");
}

它应该打印编译器的优化-它将结果输入EBX,将其移动到EAX的AL部分,对EDX进行第二次检查,然后根据EAX和EDX的比较进行分支。这样可以节省一个分支,让代码运行得更快,而不会产生任何副作用


如果您使用
-O0
而不是
-O2
进行编译,我认为它将生成更简单的程序集,更符合您的期望。

C标准只指定了“抽象机器”的行为;它不指定程序集的生成。只要程序的可观察行为与抽象机器上的行为相匹配,实现就可以使用它喜欢的任何物理机制来实现语言结构。标准(C99)中的相关章节是5.1.2.3程序执行。

正如其他人所提到的,此程序集输出是一种编译器优化,不会影响程序执行(据编译器所知)。如果要有选择地禁用此优化,则需要告诉编译器不应跨代码中的序列点对变量进行优化

序列点是控制表达式(
if
switch
while
do
for
的所有三个部分中的求值)、逻辑OR和and、条件(
?:
)、逗号和
返回
语句

要防止编译器跨这些点进行优化,必须声明变量
volatile
。在您的示例中,可以指定

volatile long a;
unsigned long m;
{...}
if (!a && m > 0x002 && m < 0x111) {...}
volatile长a;
无符号长m;
{...}
如果(!a&&m>0x002&&m<0x111){…}

这样做的原因是
volatile
用于指示编译器它无法预测等价机器相对于变量的行为。因此,它必须严格遵守代码中的顺序点。

无论哪种方式,代码的行为都是正确的(即,符合语言标准的要求)

看起来您正试图找到一种生成特定汇编代码的方法。在两个可能的汇编代码序列中(它们的行为方式相同),您会发现一个令人满意,另一个不令人满意

确保令人满意的汇编代码序列的唯一真正可靠的方法是显式地编写汇编代码。gcc支持内联汇编

C代码指定行为。汇编代码指定机器代码

但所有这些都提出了一个问题:为什么这对你很重要?(我不是说它不应该,我只是不明白为什么它应该。)


编辑:如何准确定义
a
m
?如果,正如您所建议的,它们与内存映射设备相关,那么它们应该声明为volatile,这可能正是您问题的解决方案。如果它们只是普通变量,那么编译器可以随意使用它们(只要不影响程序的可见行为),因为您没有要求它不要这样做。

也许是因为编译器知道比较POD没有副作用?这是在优化打开还是关闭的情况下编译的?我在帖子中给出了编译选项:
-g-O2
。“是的,有优化。@ RedX:这是C,不是C++。未定义“POD”,并且所有比较都没有副作用(尽管对其操作数的求值可能有副作用,在这种情况下,不可能进行此类优化)。如果其中一个操作数是函数调用且函数有副作用,则比较不是没有副作用的。在这种情况下,短路可能变得很重要。在上面的代码中,它不是。@RudyVelthuis:R.已经提到:“(尽管对它们的操作数的求值可能有副作用…)。这就是非正式的“好像”规则——编译器可以生成它喜欢的任何代码,只要外部可见的行为与在抽象机器上执行编写的代码相同。对volatile变量的访问是外部可见行为的一部分,因此如果将
m
声明为
volatile
,则您应该看到生成的不同代码。@caf:您应该添加它作为答案,我将+1它仅用于
volatile
注释。具体取决于实现,可能存在也可能不存在外部访问易失性对象的可能性。对于具有自动存储持续时间且其地址从未被占用的对象,volatile除了需要与setjmp进行交互之外,肯定不需要有任何效果
volatile long a;
unsigned long m;
{...}
if (!a && m > 0x002 && m < 0x111) {...}