Wie kann man in c in eine andere Funktion “springen”?

Lesezeit: 7 Minuten

Benutzer-Avatar
llllllllllll

Grundsätzlich versuche ich, Assembler-Code in C zu simulieren.

Hier ist der C-Code:

int main()
{
   test();
main_next:
   printf("Hello, World!");
}

void test()
{
     goto main_next;
}

Beim Versuch, diesen Code zu kompilieren (Linux 32 Bit, gcc 4.6.3), habe ich diesen Fehler erhalten:

 error: label ‘main_randomtag_next’ used but not defined

Weiß jemand, wie man diese Art von interprozeduralem goto in C durchführt?

Vielen Dank!

  • Selbst wenn dies möglich wäre, sollten Sie dies nicht können wollen.

    – Oliver Charlesworth

    25. Januar 2014 um 19:22 Uhr

  • Es ist möglich. Verwenden setjmp/longjmp. (sehen en.wikipedia.org/wiki/Setjmp.h)

    – Kyle Lutz

    25. Januar 2014 um 19:24 Uhr


  • NB: Für den Typen, der -1 gesetzt hat, habe ich +1 gesetzt, weil das OP einige Nachforschungen angestellt hat. Die Frage ist klar und legitim. Es ist nur so, dass das, was er tun möchte, schlecht ist und wir Kommentare und Antworten erhalten haben, um ihm bessere Optionen zu geben!

    – zmo

    25. Januar 2014 um 19:29 Uhr

  • Wenn Sie Assembler in reinem C simulieren, warum haben Sie dann Funktionen? Assembly hat keine Funktionen. Montage hat Stapel. Und Sie können das stattdessen simulieren.

    – Sedat Kapanoglu

    25. Januar 2014 um 19:37 Uhr

  • Dies ist eine sehr interessante Frage. Ich habe Ihren Beitrag bearbeitet, um ihn lesbarer zu machen, und darüber hinaus denke ich, dass es eine absolut gültige Frage ist.

    – michaelsnowden

    25. Januar 2014 um 19:38 Uhr

Benutzer-Avatar
John Kugelmann

Aber was ist mit der Kinder? Stapel?

goto zwischen Funktionen macht keinen Sinn, wenn Sie an den Stack denken. Was wird auf dem Stapel sein, wenn Sie springen? Die Quell- und Zielfunktionen könnten möglicherweise unterschiedliche Argumente und einen unterschiedlichen Rückgabewert haben. An wen wird die neue Funktion zurückkehren? Wird sein Rückgabewert für den Aufrufer überhaupt Sinn machen? Der Aufrufer hat die Quellfunktion aufgerufen, nicht die Zielfunktion.

Zurück zum Anrufer?

Betrachten Sie Ihr Beispiel genau:

int main()
{
   test();
main_next:
   printf("hello, world);
}

void test()
{
     goto main_next;
}

Was passiert, wenn die goto führt aus? Ich nehme an, Sie möchten, dass dies springt den Stapel hoch zurück zur Berufung main() Funktion. Das goto wäre effektiv dasselbe wie a returnAufrufliste ändern von:

main()                            main()
|                   to            
+--> test()                       

Aber was ist, wenn Sie zu einer Funktion springen möchten, die sich nicht in der Aufrufliste befindet? Was dann?

Oder ersetzen Sie die aktuelle Funktion?

Eine andere Interpretation ist, dass die goto würde das bestehende ersetzen test() mit einem anrufen main(). Der Aufrufstapel würde sich ändern von:

main()                            main()
|                   to            |
+--> test()                       +--> main()

Jetzt main() sich selbst rekursiv aufruft, und das untere main() wird nach oben zurückkehren main()– der übrigens a erwartet void Rückgabewert, wird aber eine erhalten int.

setjmp und longjmp

Am nächsten kommt man mit setjmp / longjmp. Diese ermöglichen es Ihnen, den Stack-Kontext für nicht lokales goto zu speichern und wiederherzustellen, sodass Sie zwischen Funktionsaufrufen springen können.

setjmp und longjmp Umgehen Sie die von mir beschriebenen Probleme, indem Sie (a) den vollständigen Stack-Kontext beim Springen speichern und wiederherstellen und (b) keine Sprünge zulassen, wenn der Stack-Kontext nicht mehr gültig ist. Ich zitiere aus die Manpage (Hervorhebung von mir):

setjmp() und longjmp(3) sind nützlich, um mit Fehlern und Interrupts umzugehen, die in einer Low-Level-Subroutine eines Programms auftreten. setjmp() speichert den Stack-Kontext/die Umgebung in env zur späteren Verwendung durch longjmp(3). Der Stapelkontext wird ungültig gemacht, wenn die Funktion, die setjmp() aufgerufen hat, zurückkehrt.

Um es anders zu sagen, longjmp ist im Grunde das C-Äquivalent von eine Ausnahme werfen. Eine Funktion auf niedriger Ebene kann den Aufrufstapel entladen und die Ausführung bei einer Funktion auf viel höherer Ebene fortsetzen.

Es ist auch schrecklich schwierig zu bedienen und selten eine gute Idee. Nochmal aus der Manpage:

setjmp() und sigsetjmp() erschweren das Verständnis und die Wartung von Programmen. Wenn möglich, sollte eine Alternative verwendet werden.

GCC generiert zuerst eine Assembly-Datei und baut sie erst dann zusammen. Wie sieht es also mit der Erstellung von Etiketten mit Inline-Assemblierung aus?

void test()
{
    __asm__ volatile ( 
         "jmp main_next"
    );
}


int main()
{
    test();
    __asm__ volatile ( 
        "main_next:"
    );
    printf("hello, world");
}

Dies sollte jedoch (offensichtlich) in realen Fällen nicht verwendet werden, da es sich überhaupt nicht um den Stapel kümmert.

  • Dies ist nur eine von vielen Möglichkeiten, ein nicht funktionierendes Programm zu erstellen, indem Sie inline asm verwenden, um etwas zu tun, was der Compiler erwartet nicht machen. Sie können verwenden asm goto um dem Compiler mitzuteilen, wohin Ihr Asm springen kann, aber nur innerhalb der aktuellen Funktion aus dem gleichen Grund, den Johns Antwort erklärt.

    – Peter Cordes

    30. Dezember 2016 um 1:58 Uhr

  • @PeterCordes Der Grund, warum ich diese Frage überprüft habe, ist, dass ich den aktuellen Befehlszeiger NICHT auf den Stapel schieben möchte, wenn ich den Aufruf ausführe. Ich teile das gleiche Ziel wie der ursprüngliche Beitrag und interpretiere einen Bytecode. Aber ich beabsichtige, dies mit Threaded-Code zu tun, bei dem jede Anweisung auf die nächste springt, ohne dass Stapelmagie erforderlich ist.

    – Raslanowe

    26. Dezember 2020 um 15:27 Uhr

  • @Raslanove: Wenn Sie möchten, dass das funktioniert, muss sich der gesamte Code sowieso in derselben Funktion befinden, damit er möglicherweise sicher ist, und dann können Sie einfach normal verwenden goto oder switch. Du kannst noch nie Lassen Sie die Ausführung sicher am Ende einer asm-Anweisung herausfallen, wenn die Ausführung nicht am Anfang dieser asm-Anweisung eingetreten ist. (Außer vielleicht in einem Debug-Build, bei dem jede C-Anweisung in einen separaten Block kompiliert wird, sodass Sie mit einem Debugger zwischen Zeilen springen können, aber Debug-Builds sind nicht mit der Leistung kompatibel.)

    – Peter Cordes

    26. Dezember 2020 um 19:50 Uhr

  • @Raslanove: Siehe auch X86-Prefetching-Optimierungen: Threaded-Code “computed goto” bezüglich: Jump-Threading im Vergleich zu einer einzigen indirekten Verzweigung, die alles verteilt. Letzteres ist normalerweise mit modernen Branchenvorhersagestrategien (IT-TAGE) in Haswell und später in Ordnung, aber siehe auch emulators.com/docs/nx25_nostradamus.htm für einen guten alten Artikel über Jump-Threading.

    – Peter Cordes

    26. Dezember 2020 um 19:58 Uhr

  • @PeterCordes Ich bin zu dem gleichen Schluss gekommen, alles in derselben Funktion zu haben, ist Compiler-freundlich. Und ich kann sogar ganz auf die Verwendung von Assembly verzichten und den Vorteil nutzen, plattformübergreifend zu sein. Stattdessen habe ich am Ende “void* ptr = &&labell;” verwendet. und “gehe zu *ptr;”. Wir können unsere Nachschlagetabellen aufbauen und sogar Zeigerarithmetik verwenden. Und tolle Referenzen übrigens. Vielen Dank 🙂

    – Raslanowe

    3. Januar 2021 um 16:25 Uhr

Nun, ich kann es nicht besser sagen als die Weisheit von http://c-faq.com/style/stylewars.html !

Wenn Sie das Verhalten von ASM nur mit C emulieren möchten, sollten Sie grundsätzlich alle Verzweigungsfähigkeiten von C/C++ nutzen. Und die Verwendung von Funktionen und des Funktionsstapels ist tatsächlich eine Verbesserung gegenüber Gotos und Tags. Darum geht es bei der strukturierten Programmierung, wie @ssg weise sagte!

  • Es ist nicht in allen Fällen eine Verbesserung, und daher hängt es davon ab zu sagen, dass es eine Verbesserung ist. Gotos und Labels sind flexibler und behalten denselben Stack-Kontext bei.

    – Andreas

    15. Juni 2021 um 10:30 Uhr


Es ist nicht erlaubt, von einer Funktion in eine andere zu springen. Das Problem ist, dass die Funktion “test” eine Rücksprungadresse auf dem Stack und möglicherweise einen Rahmen für Variablen hat. Um dies zu tun, sollten Sie also den optionalen Rahmen bereinigen und die Adresse auf dem Stapel mit der Adresse von main_next ändern:

In diesem elementaren Beispiel sollten Sie also einfach statt goto main_next return schreiben.

Aber in anderen Fällen ist es etwas komplizierter, weil Sie verstehen müssen, was Sie wollen.

Benötigen Sie den Code nach main_next: als ob er in test() geschrieben worden wäre? Sie sollten daran denken, dass die lokalen Variablenrahmen dieser beiden Funktionen unterschiedlich sind. Das bedeutet, dass Sie, wenn Sie nur einen Sprung machen, die Namen der in main verwendeten Variablen verwenden, sich aber auf den von test() erstellten Stapelrahmen beziehen. Das heißt, wenn die beiden Frames nicht kompatibel sind, können sehr seltsame Dinge passieren.

Das Problem ist, was Sie genau wollen und warum?

Wenn Sie nur an die Assemblierung denken und keine Variablen in Stapelrahmen verwenden, ist dies in Ordnung. Aber was werden Sie ohne Variablen tun?

Es gibt Möglichkeiten zu tun, was Sie wollen, aber Sie sollten entscheiden, was genau Sie brauchen, und ich kann Ihnen sagen, wie es getan werden kann!

1364360cookie-checkWie kann man in c in eine andere Funktion “springen”?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy