Any good compiler evaluates simple expressions at compile-time. The behavior you are seeing is unrelated to preprocessing. If you directly write:
int a = 365 * 24;
then any good compiler will generate code that initializes a
to 8760. (Some compilers might not if they are executed with their optimization features disabled. But any decent compiler will perform this optimization when optimization is enabled.)
In modern compilers, optimization will do a great deal more than this. For example, they will identify invariant code outside of loops. (That is, if there are expressions or even full statements inside of a loop whose results do not depend on the data in the current loop iteration, they will move those expressions or statements outside of the loop, so that they are only evaluated once instead of each time the loop executes.) They will remove objects that are never used. If one path of a conditional branch has undefined behavior in some circumstances, they may deduce that that path is never taken, so they will optimize code by removing that path and the conditional branch in those circumstances. They may even replace entire loops with equivalent mathematical expressions. For example, when I compile:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int n = atoi(argv[1]);
int s = 0;
for (int i = 1; i <= n; ++i)
s += i;
printf("%d\n", s);
}
with Apple LLVM 9.1.0 (clang-902.0.39.2), the resulting assembly code does not contain a loop. After calling atoi
, it executes instructions that are, in effect1, printf("%d\n", 0 < n ? n*(n+1)/2 : 0);
. So the loop is entirely replaced by a test and a simple arithmetic expression.
Footnote
1 The actual instructions are:
leal -1(%rax), %ecx
movl %eax, %edx
addl $-2, %edx
imulq %rcx, %rdx
shrq %rdx
leal -1(%rdx,%rax,2), %esi
A closer representation of those is (n-1)*(n-2)/2 + 2*n - 1
. One reason for the compiler to do this is that it gets the “correct” result if n
is INT_MAX
, as n*(n+1)/2
would produce zero because n+1
wraps, whereas (n-1)*(n-2)/2 + 2*n - 1
gets the same result that the loop gets (assuming wrapping arithmetic), which is INT_MAX+1
or INT_MIN
due to wrapping. By C rules, the compiler could have used the simpler n*(n+1)/2
since the behavior on overflow is not defined by C, but this compiler may be conforming to stricter rules such as using wrapping two’s complement arithmetic.