2016-10-14 5 views
2

Im folgende Beispiel:Compile-Zeit konst und formale Parameter

static inline void foo(const int varA) 
{ 
    ... 
    __some_builtin_function(varA); 
    ... 
} 

int main() 
{ 
    foo(10); 
    return 0; 
} 

Ist varA hält hier als Compile-Zeit konstant?

Bitte beachten Sie, dass ich mit C arbeite, nicht mit C++.

Jeder Verweis auf den Standard oder eine solche zuverlässige Dokumentation, die Kompilierzeitkonstanten und insbesondere deren Beziehung zu den formalen Parametern beschreibt, wäre sehr willkommen.

Antwort

3

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 
+0

Vielen Dank für solch eine detaillierte Erklärung. – Arsen

2

Nein, varA ist keine Kompilierzeitkonstante, es ist nur eine const int Variable, was bedeutet, dass der Wert innerhalb der Funktion foo() nicht geändert werden kann. Der Compiler könnte jedoch folgern, dass Sie diese Funktion mit der Kompilierzeitkonstante 10 als Argument aufrufen und eine Version dieser Funktion kompilieren, bei der jedes Auftreten von varA durch 10 ersetzt wird.