Short circuit on boolean operands without side effects

For the bounty: how can this behavior be disabled in each case without disabling or reducing the level of optimization?

The following conditional expression was compiled on MinGW GCC 3.4.5, where a is of type signed long and m is of type unsigned long .

 if (!a && m > 0x002 && m < 0x111) 

The CFLAGS used were -g -O2 . Here is the corresponding output of the GCC assembly (reset with 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 you can easily follow how to evaluate !a first and then evaluate m > 0x002 . The first transition does not occur until 133 . By this time, the two expressions were evaluated, regardless of the result of the first expression !a If a was zero, the expression can (and should) be immediately terminated, which is not done here.

How does this relate to the C standard, which requires Boolean operators to close as soon as the result can be determined?

+7
source share
5 answers

As others noted, this assembly is a compiler optimization that does not affect program execution (as far as the compiler can tell). If you want to selectively disable this optimization, you need to tell the compiler that your variables should not be optimized at sequence points in the code.

Sequence points are control expressions (evaluations in if , switch , while , do and all three sections of for ), logical OR and AND, legend ( ?: , Commas and the return .

To prevent compiler optimization at these points, you must declare your volatile variable. In your example, you can specify

 volatile long a; unsigned long m; {...} if (!a && m > 0x002 && m < 0x111) {...} 

The reason for this is that volatile used to tell the compiler that it cannot predict the behavior of an equivalent machine relative to a variable. Therefore, it must strictly obey the sequence points of your code.

+5
source

The C standard only specifies the behavior of an "abstract machine"; it does not determine assembly generation. As long as the observed behavior of the program coincides with the observed with respect to the abstract machine, the implementation can use any physical mechanism that he likes to implement language constructs. The relevant section in the standard (C99) is 5.1.2.3 Program Execution.

+10
source

This is probably a compiler optimization, since comparing integral types has no side effects. You can try to compile without optimization, or use a function that has side effects instead of the comparison operator, and see if that does it all.

For example, try

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

and he should print ac

+6
source

Compiler optimization - it gets the result in EBX, moves it to AL, part of EAX, performs a second check in EDX, and then conducts it based on a comparison of EAX and EDX. This saves the branch and leaves the code faster, without any differences in side effects.

If you compile with -O0 rather than -O2 , I believe this will create a more naive assembly that more closely matches your expectations.

+4
source

The code behaves correctly (i.e., in accordance with the requirements of the language standard) in any case.

It looks like you are trying to find a way to generate specific assembly code. Of the two possible assembly code sequences, both of which behave the same, you find one satisfactory and the other unsatisfactory.

The only reliable way to guarantee a satisfactory assembly code sequence is to write the assembly code explicitly. gcc supports inline build.

C code indicates behavior. The assembly code indicates the machine code.

But all this poses the question: why is this important to you? (I am not saying that it should not, I just do not understand why it should be.)

EDIT: How exactly are a and m defined? If, as you think, they are connected with memory-mapped devices, then they should be declared volatile - and this may be the solution to your problem. If they are just ordinary variables, then the compiler can do whatever they like with them (as long as this does not affect the program’s visible behavior), because you didn’t ask for it.

+3
source

All Articles