Nein, varA
ist keine Kompilierzeitkonstante - sie kann bei jedem Aufruf der Funktion durchaus unterschiedlich sein. Konstanten haben eine bestimmte Definition in der Norm - einige der wichtigsten Details sind in this answer berührt, oder Sie können nur den Standard für das offizielle Wort lesen.
Das heißt, was Sie wissen möchten, ist, wenn der Compiler behandeln es als eine Konstante, in Fällen, wo Sie es mit einem konstanten Wert wie in Ihrem Beispiel nennen. Die Antwort ist "Ja" für jeden vernünftigen Compiler mit optimierter Optimierung. Call Inlining und konstante Verbreitung sind die Magie, die dies möglich machen. Der Compiler wird versuchen, den Aufruf an foo
anzufügen und dann 10
für das Argument zu ersetzen, und das wird rekursiv folgen.
Werfen wir einen Blick auf Ihr Beispiel. Ich habe es leicht geändert, um Return foo (10) in main
zu verwenden, damit der Compiler nicht alles vollständig weg optimiert! Ich habe auch gcc __builtin_popcount
als die unspezifizierte Funktion gewählt foo()
genannt. Check out this godbolt version Ihres Programms ohne Optimierung, in gcc 6.2 kompiliert. Die Baugruppe sieht folgendermaßen aus:
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
Es ist einfach. Die meisten der foo()
ist nur Einrichten des Stack-Frame und (sinnlos) schieben edi
(die varA
Argument) auf dem Stapel. Wenn wir foo()
von main aufrufen, übergeben wir 10
als Argument. Die Tatsache, dass es eine Konstante ist, hat nicht geholfen.
OK, lassen Sie uns dies mit einer realistischeren -O2
Einstellung kompilieren. Here's what we get:
main:
mov eax, 2
ret
Das ist es. Die ganze Sache ist nur return 2
, ziemlich. So konnte der Compiler definitiv sehen, dass 10 ein konstanter Wert ist, und expandieren foo(10)
. Darüber hinaus war es in der Lage, foo(10)
vollständig auszuwerten, die popcount von 10 (0b1010 in binär) direkt zu berechnen, ohne die popcount
Anweisung überhaupt zu benötigen, und nur die Antwort 2
zurückzugeben.
Beachten Sie auch, dass der Compiler überhaupt keinen Code für foo()
alle generiert hat. Das ist, weil es sehen kann, dass es static inline
erklärt wird, so dass es nur innerhalb dieser Kompilierungseinheit aufgerufen werden kann, und dass es tatsächlich keine Anrufer gibt, die die volle Funktion benötigen, da die einzige Call-Site inline war. So verschwindet foo einfach.
Also, was der Standard sagt über Konstanten Zeit zu kompilieren hilft nur zu verstehen, was ein Compiler muss tun, und wo bestimmte Ausdrücke sein rechtlich verwendet, aber es hilft nicht viel zu verstehen, was ein Compiler wird in der Praxis mit Optimierung tun.
Der Schlüssel hier war, dass Ihre Methode foo()
in der gleichen Kompilierungseinheit als sein Aufrufer deklariert ist, so dass der Compiler inline und effektiv über die beiden Funktionen optimieren konnte. Wenn es in einer separaten Kompilierungseinheit wäre, könnte dies nicht passieren, es sei denn, Sie verwenden einige Optionen wie die Generierung von Link-Time-Code.
Wie sich herausstellt, so ziemlich jede Optimierung hier ergibt sich der gleiche Code-Einstellung, wie die Transformation ziemlich trivial ist.
Tatsächlich entweder von inline
oder static
genug ist, um die Funktion lokal zu der Übersetzungseinheit zu machen. Wenn Sie beide weglassen, wird jedoch ein Body für foo()
generiert, da er von einer separat kompilierten Einheit aufgerufen werden könnte. Mit der Optimierung sieht der Körper wie folgt aus:
foo(int):
xor eax, eax
popcnt eax, edi
ret
Vielen Dank für solch eine detaillierte Erklärung. – Arsen