GCC -O3优化级别下,'%2'与'&1'未生成相同汇编指令的原因探究
x % 2 to x & 1? First, let's recap the code and assembly you provided to set the context:
Bitwise AND implementation
int main(int argc, char **argv){ return argc & 1; }
Corresponding assembly:
0000000000001020 <main>: 1020: 89 f8 mov %edi,%eax 1022: 83 e0 01 and $0x1,%eax 1025: c3 ret 1026: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
Modulo implementation
int main(int argc, char **argv){ return argc % 2; }
Corresponding assembly:
0000000000001020 <main>: 1020: 89 fa mov %edi,%edx 1022: c1 ea 1f shr $0x1f,%edx 1025: 8d 04 17 lea (%rdi,%rdx,1),%eax 1028: 83 e0 01 and $0x1,%eax 102b: 29 d0 sub %edx,%eax 102d: c3 ret 102e: 66 90 xchg %ax,%ax
Great question! The core reason GCC doesn't optimize argc % 2 to argc & 1 is that these operations produce different results when argc is negative—and the compiler must strictly adhere to the C standard's rules for integer modulus.
The C Standard Difference
In C, the result of a % b must share the same sign as a. Let's compare for negative values:
- For an odd negative number like
-1:argc & 1returns1(since the least significant bit of any odd two's-complement integer is 1)argc % 2returns-1(per C standard, because-1 / 2truncates to0, so-1 - (2 * 0) = -1)
- For an even negative number like
-2:argc & 1returns0argc % 2returns0(this matches, but the odd case breaks equivalence)
Since the compiler can't assume argc is always non-negative (even though it's rare in practice), it has to generate code that handles negative inputs correctly.
Breaking Down the Modulo Assembly
Let's walk through each instruction in the modulo implementation to see how it enforces the C standard:
mov %edi,%edx: Copiesargc(from%edi) into%edx.shr $0x1f,%edx: Logically shifts%edxright by 31 bits. For a 32-bit integer:- Positive
argc→ result is0 - Negative
argc→ result is1(the highest sign bit is isolated)
- Positive
lea (%rdi,%rdx,1),%eax: Calculatesargc + %edx. For negativeargc, this isargc + 1; for positive, it's justargc.and $0x1,%eax: Takes the least significant bit of the previous result—this gives us a value equivalent to(argc + 1) & 1for negatives, orargc & 1for positives.sub %edx,%eax: Subtracts the sign bit. For negativeargc, this turns0(from step 4 for odd values) into-1, which matches the required modulus result.
Optional: Force the Optimization
If you want GCC to optimize %2 to &1, you can explicitly tell the compiler that argc is non-negative using a built-in assumption:
int main(int argc, char **argv){ __builtin_assume(argc >= 0); return argc % 2; }
This will generate the same assembly as the bitwise AND version, since GCC now knows it doesn't need to handle negative inputs.
内容的提问来源于stack exchange,提问作者Stypox

