Zum Thema Lambdas würde ich mal etwas mehr ausholen:
In C (nicht C++) erlaubt gcc verschachtelte Funktionen.
Eine verschachtelte Funktion ist nichts weiter als eine normale Top-Level-Funktion mit verstecktem Bezeichner und Zugriff auf die lokalen Variablen der umgebenden Funktion. Dazu übergibt gcc einen (unsichtbaren) Zeiger auf den Stackframe, kann aber auch entsprechende Optimierungen vornehmen. Genauso tut es Turbo Pascal und ähnliche Programmiersprachen mit verschachtelten Funktionen. Sogar auf x86-Assemblerniveau gibt es dafür einen Befehl, nämlich "enter".
In C++ sind verschachtelte Funktionen verboten, denn dafür gibt es die Lambdas. Das C++-Äquivalent der verschachtelten C-Funktion ist:
1 |
auto funcname = [&](parameter) {Aktionen;}
|
Die "Aktionen" können wie in C auf die Variablen der umgebenden Funktion lesend und verändernd zugreifen, das macht das "&" in den eckigen Klammern, der sogenannte "Capture-Ausdruck".
Man kann dem Compiler bei der Optimierung enorm helfen, wenn man in die eckigen Klammern keine oder nur die Variablen aufführt, die tatsächlich von "Aktionen" benötigt werden, mit "=" als Kopie und mit "&" als Referenz.
Solange "funcname" nur innerhalb der umgebenden Funktion aufgerufen wird, bleibt alles halbwegs übersichtlich. Kompliziert wird es, wenn "funcname" aufgerufen werden soll, wenn die umgebende Funktion beendet wurde! Dann muss gcc den Capture in ein Funktionsobjekt "einfrieren" und auf den Heap kopieren. Das erfordert new bzw. malloc() und eine entsprechend betriebsbereite Heap-Konfiguration, für 8-Bit-Controller Irrsinn.
Dies passiert für den Compiler in dem Moment, wenn man "funcname" irgendwohin zuweist oder per return zurückgibt.
Der Klassiker hierfür sind solche Funktionen wie qsort(), denen man gerne ein Lambda als Sortierkriterium übergeben möchte. Da der C++-Compiler "weiß", dass qsort() den Zeiger nur als Callback aufruft (und währenddessen der Stackframe gültig bleibt) wird in diesem Fall (als eine Lösungsmöglichkeit) auf dem Stack ein Trampolin (ein Stück Code zum Liefern der Stapelrahmen-Adresse) gelegt und diese Adresse dem qsort() übergeben. Das geht so nicht Harvard-Architektur: Beim AVR geht das bspw. mit einer Lambda-Version von qsort() welches ein Funktionsobjekt statt einem Funktionszeiger erwartet. Dieses kann auf dem Stack liegen.
Nur für den Fall eines leeren Capture:
1 |
auto funcname = [](parameter) {Aktionen;}
|
braucht der Compiler keine Bocksprünge machen, und "funcname" ist ein ganz normaler Funktionszeiger ohne versteckten Stackframe-Zeiger.
Es ist verboten, den Zeiger einer verschachtelten C-Funktion nach außen zu geben, und wenn man es trotzdem irgendwie tut, wird beim Aufruf ein heilloses Chaos ausbrechen, wenn darin auf Werte der (nicht mehr laufenden) Umgebungsfunktion zugegriffen wird. Das war auch bei Turbo Pascal so.