In the following example:
static inline void foo(const int varA)
{
...
__some_builtin_function(varA);
...
}
int main()
{
foo(10);
return 0;
}
Is varA here considers as a compile-time constant?
Please note that I am working with C, not with C++.
Any link to the standard or such a reliable documentation describing compile-time constants and particularly their relation to the formal parameters would be really appreciated.
No, varA
is not a compile time constant - it can certainly be different every time the function is called. Constants have a specific definition in the standard - some of the key details are touched on in this answer, or you can just read the standard for the official word.
That said, what you may want to know is if the compiler will treat it as a constant, in cases where you call it with a constant value as in your example. The answer is "yes" for any decent compiler with optimization turned on. Call inlining and constant propagation are the magic that make this happen. The compiler will try to inline the call to foo
and then substitute 10
for the argument, and will follow that recursively.
Let's take a look at your example. I've slightly modified it to use return foo(10) in main
so that the compiler doesn't optimize everything away entirely! I've also chosen gcc's __builtin_popcount
as the unspecified function called by foo()
. Check out this godbolt version of your program without optimization, compiled in gcc 6.2. The assembly looks like:
foo(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
popcnt eax, eax
pop rbp
ret
main:
push rbp
mov rbp, rsp
mov edi, 10
call foo(int)
pop rbp
ret
It's straightforward. Most of the foo()
is just setting up the stack frame and (pointlessly) pushing edi
(the varA
argument) on to the stack.
When we call foo()
from main, we pass 10
as the argument. So clearly the fact that it's a constant hasn't helped.
OK, let's compile this with a more realistic -O2
setting1. Here's what we get:
main:
mov eax, 2
ret
That's it. The whole thing is just return 2
, pretty much. So the compiler was definitely able to see that 10 is a constant value, and expand foo(10)
. Furthermore, it was able to evaluate foo(10)
completely, calculating the popcount of 10 (0b1010 in binary) directly, without needing the popcount
instruction at all, and just returning the answer 2
.
Also note that the compiler didn't even generate any code for foo()
all. That's because it can see it is declared static inline
2 so it can only be called from within this compilation unit, and that there are actually no callers that need the full function since the only call-site was inlined. So foo just disappears.
So what the standard says about compile-time constants only helps in understanding what a compiler must do, and where certain expressions may be legally used, but it doesn't help much in understanding what a compiler will do in practice with optimization.
The key here was that your method foo()
is declared in the same compilation unit as its caller, so the compiler could inline and effectively optimize across the two functions. If it were in a separate compilation unit, this could not happen, unless you use some options such as link-time code generation.
1As it turns out, pretty much any optimization setting here results in the same code, as the transformation is pretty trivial.
2In fact, either of inline
or static
is enough to make the function local to the compilation unit. If you omit both, however, a body for foo()
will be generated since it could be called from a separately compiled unit. With optimization, the body looks like:
foo(int):
xor eax, eax
popcnt eax, edi
ret