Sie können diese Dinge tun, vor allem, weil sie eigentlich gar nicht so schwer zu tun sind.
Aus Sicht des Compilers ist die Implementierung einer Funktionsdeklaration in einer anderen Funktion ziemlich trivial. Der Compiler benötigt einen Mechanismus, mit dem Deklarationen innerhalb von int x;
Funktionen ohnehin andere Deklarationen (z. B. ) innerhalb einer Funktion verarbeiten können.
Es verfügt normalerweise über einen allgemeinen Mechanismus zum Parsen einer Deklaration. Für den Typ, der den Compiler schreibt, ist es überhaupt nicht wichtig, ob dieser Mechanismus beim Parsen von Code innerhalb oder außerhalb einer anderen Funktion aufgerufen wird - es ist nur eine Deklaration. Wenn er also genug sieht, um zu wissen, dass es eine Deklaration gibt, Es ruft den Teil des Compilers auf, der sich mit Deklarationen befasst.
Tatsächlich würde das Verbot dieser bestimmten Deklarationen innerhalb einer Funktion wahrscheinlich zu einer zusätzlichen Komplexität führen, da der Compiler dann eine völlig kostenlose Überprüfung benötigen würde, um festzustellen, ob er bereits Code in einer Funktionsdefinition betrachtet, und auf dieser Grundlage entscheiden, ob diese bestimmte Deklaration zugelassen oder verboten werden soll Erklärung.
Damit bleibt die Frage, wie sich eine verschachtelte Funktion unterscheidet. Eine verschachtelte Funktion unterscheidet sich dadurch, wie sie sich auf die Codegenerierung auswirkt. In Sprachen, die verschachtelte Funktionen zulassen (z. B. Pascal), erwarten Sie normalerweise, dass der Code in der verschachtelten Funktion direkten Zugriff auf die Variablen der Funktion hat, in der er verschachtelt ist. Zum Beispiel:
int foo() {
int x;
int bar() {
x = 1;
}
}
Ohne lokale Funktionen ist der Code für den Zugriff auf lokale Variablen ziemlich einfach. In einer typischen Implementierung wird beim Eintritt der Ausführung in die Funktion ein Speicherplatzblock für lokale Variablen auf dem Stapel zugewiesen. Alle lokalen Variablen werden in diesem einzelnen Block zugewiesen, und jede Variable wird einfach als Versatz vom Anfang (oder Ende) des Blocks behandelt. Betrachten wir zum Beispiel eine Funktion wie diese:
int f() {
int x;
int y;
x = 1;
y = x;
return y;
}
Ein Compiler (vorausgesetzt, er hat den zusätzlichen Code nicht optimiert) generiert möglicherweise Code für diesen ungefähr äquivalenten Code:
stack_pointer -= 2 * sizeof(int);
x_offset = 0;
y_offset = sizeof(int);
stack_pointer[x_offset] = 1;
stack_pointer[y_offset] = stack_pointer[x_offset];
return_location = stack_pointer[y_offset];
stack_pointer += 2 * sizeof(int);
Insbesondere hat es eine Position, die auf den Anfang des Blocks lokaler Variablen zeigt, und jeder Zugriff auf die lokalen Variablen erfolgt als Offsets von dieser Position.
Bei verschachtelten Funktionen ist dies nicht mehr der Fall. Stattdessen hat eine Funktion nicht nur Zugriff auf ihre eigenen lokalen Variablen, sondern auch auf die Variablen, die für alle Funktionen lokal sind, in denen sie verschachtelt ist. Anstatt nur einen "stack_pointer" zu haben, aus dem ein Offset berechnet wird, muss er den Stack zurückgehen, um die stack_pointers lokal für die Funktionen zu finden, in denen er verschachtelt ist.
In einem trivialen Fall ist das auch nicht allzu schrecklich - wenn bar
es in verschachtelt ist foo
, bar
kann es einfach den Stapel am vorherigen Stapelzeiger nachschlagen, um auf die foo
Variablen zuzugreifen . Richtig?
Falsch! Nun, es gibt Fälle, in denen dies wahr sein kann, aber es ist nicht unbedingt der Fall. Insbesondere bar
könnte rekursiv sein, in welchem Fall ein gegebener Aufruf vonbar
Möglicherweise müssen Sie eine nahezu beliebige Anzahl von Ebenen im Stapel suchen, um die Variablen der umgebenden Funktion zu finden. Im Allgemeinen müssen Sie eines von zwei Dingen tun: Entweder Sie legen einige zusätzliche Daten auf den Stapel, damit dieser zur Laufzeit den Stapel erneut durchsuchen kann, um den Stapelrahmen der umgebenden Funktion zu finden, oder Sie übergeben effektiv einen Zeiger auf Der Stapelrahmen der umgebenden Funktion als versteckter Parameter für die verschachtelte Funktion. Oh, aber es gibt auch nicht unbedingt nur eine umgebende Funktion - wenn Sie Funktionen verschachteln können, können Sie sie wahrscheinlich (mehr oder weniger) beliebig tief verschachteln, sodass Sie bereit sein müssen, eine beliebige Anzahl versteckter Parameter zu übergeben. Das bedeutet, dass Sie normalerweise so etwas wie eine verknüpfte Liste von Stapelrahmen mit umgebenden Funktionen erhalten.
Dies bedeutet jedoch, dass der Zugriff auf eine "lokale" Variable möglicherweise keine triviale Angelegenheit ist. Das Finden des richtigen Stapelrahmens für den Zugriff auf die Variable kann nicht trivial sein, sodass der Zugriff auf Variablen der umgebenden Funktionen (zumindest normalerweise) langsamer ist als der Zugriff auf wirklich lokale Variablen. Und natürlich muss der Compiler Code generieren, um die richtigen Stapelrahmen zu finden, über eine beliebige Anzahl von Stapelrahmen auf Variablen zuzugreifen und so weiter.
Dies ist die Komplexität, die C durch das Verbot verschachtelter Funktionen vermieden hat. Nun ist es sicher wahr, dass ein aktueller C ++ - Compiler eine ganz andere Art von Biest ist als ein C-Compiler aus den 1970er Jahren. Bei Dingen wie mehrfacher virtueller Vererbung muss sich ein C ++ - Compiler in jedem Fall mit Dingen befassen, die dieselbe allgemeine Natur haben (dh in solchen Fällen kann es auch nicht trivial sein, den Speicherort einer Basisklassenvariablen zu finden). Auf prozentualer Basis würde die Unterstützung verschachtelter Funktionen einem aktuellen C ++ - Compiler nicht viel Komplexität verleihen (und einige, wie z. B. gcc, unterstützen sie bereits).
Gleichzeitig wird auch selten viel Nutzen hinzugefügt. Insbesondere wenn Sie etwas definieren möchten, das sich wie eine Funktion innerhalb einer Funktion verhält, können Sie einen Lambda-Ausdruck verwenden. Was dies tatsächlich erzeugt, ist ein Objekt (dh eine Instanz einer Klasse), das den Funktionsaufrufoperator ( operator()
) überlastet, aber dennoch funktionsähnliche Funktionen bietet. Es macht das Erfassen (oder Nicht-Erfassen) von Daten aus dem umgebenden Kontext jedoch expliziter, wodurch vorhandene Mechanismen verwendet werden können, anstatt einen völlig neuen Mechanismus und ein Regelwerk für seine Verwendung zu erfinden.
Fazit: Auch wenn es zunächst so aussieht, als wären verschachtelte Deklarationen schwierig und verschachtelte Funktionen trivial, ist mehr oder weniger das Gegenteil der Fall: Verschachtelte Funktionen sind tatsächlich viel komplexer zu unterstützen als verschachtelte Deklarationen.
one
eine Funktion ist , Definition , sind die anderen zwei Erklärungen .