Ist es möglich, die Adresse eines Labels in einer Variablen zu speichern und mit goto dorthin zu springen?

Lesezeit: 10 Minuten

Benutzeravatar von Joshua Cheek
Joshua Wange

Ich weiß, jeder hasst Gotos. In meinem Code bieten sie aus Gründen, die ich in Betracht gezogen habe und mit denen ich mich wohl fühle, eine effektive Lösung (dh ich suche nicht nach “tu das nicht” als Antwort, ich verstehe deine Vorbehalte und verstehe, warum ich sie verwende ohnehin).

Bisher waren sie fantastisch, aber ich möchte die Funktionalität so erweitern, dass ich im Wesentlichen in der Lage sein muss, Verweise auf die Labels zu speichern und später darauf zuzugreifen.

Wenn dieser Code funktionieren würde, würde er die Art von Funktionalität darstellen, die ich benötige. Aber es funktioniert nicht, und 30 Minuten googeln hat nichts ergeben. Hat jemand irgendwelche Ideen?

int main (void)
{
  int i=1;
  void* the_label_pointer;

  the_label:

  the_label_pointer = &the_label;

  if( i-- )
    goto *the_label_pointer;

  return 0;
}

  • Können Sie erklären, warum Sie die Labels in Zeigern speichern müssen?

    – Ahmed

    22. November 2009 um 6:23 Uhr

  • Ich implementiere einen endlichen Zustandsautomaten, basierend auf der Antwort von Remo.D in diesem Beitrag. Es war bisher effektiv, aber ich möchte den Zuständen einen Kontext zur Verfügung stellen, in dem sie auf den aufrufenden Zustand und den aktuellen Zustand zugreifen können, entweder über einige Variablen, die bei Zustandsübergängen gesetzt werden, oder über einen Rückruf oder so etwas.

    – Joshua Wange

    22. November 2009 um 6:36 Uhr

  • Duplikat von stackoverflow.com/questions/938518/cc-goto

    – qrdl

    22. November 2009 um 7:52 Uhr

Benutzeravatar von Michael Aaron Safyan
Michael Aaron Safjan

Die C- und C++-Standards unterstützen diese Funktion nicht.

Die GNU Compiler Collection (GCC) enthält jedoch eine nicht standardmäßige Erweiterung, um dies zu tun, wie in beschrieben Labels als Werte Abschnitt der Verwenden der GNU-Compiler-Sammlung Handbuch.

Im Wesentlichen haben sie einen speziellen unären Operator hinzugefügt && die die Adresse des Etiketts als Typ meldet void *. Einzelheiten finden Sie im Artikel. Mit dieser Erweiterung könnten Sie einfach verwenden && Anstatt von & in Ihrem Beispiel, und es würde auf GCC funktionieren.

PS Ich weiß, du willst nicht, dass ich es sage, aber ich sage es trotzdem … MACHT DAS NICHT!!!

  • goto label address eignet sich hervorragend zum Schreiben eines Interpreters.

    – Justin Dennahower

    5. November 2013 um 17:22 Uhr

  • Ich würde gerne wissen, warum um alles in der Welt doppelte kaufmännische Und-Zeichen (logisches Und) verwendet wurden, obwohl das vorhandene „&“ zum Abrufen der Adresse eines Bezeichners am sinnvollsten gewesen wäre. Der einzige Grund, warum ich mir vorstellen kann, ist, dass Label-Identifikatoren in einem parallelen, aber separaten Bereich als Variablen-Identifikatoren zu existieren scheinen, und daher könnte es zu Unklarheiten zwischen dem Abrufen der Adresse eines Labels und einer Variablen kommen, wenn beide gleich benannt würden (wohl aber das ist nur schlechte Praxis, ein int foo und foo: in derselben Funktion zu deklarieren). Wenn dies jemals in den Standard aufgenommen wird, würde ich auf ‘&’ hoffen, nicht auf ‘&&’.

    – Dwayne Robinson

    2. November 2014 um 12:25 Uhr


  • Mach es total. Wenn Sie eine Interpreter-Schleife schreiben, ist dies der richtige Weg.

    – Kariddi

    28. September 2017 um 17:42 Uhr

  • @JustinDennahower: CPython verwendet es seit Jahren für diesen Zweck (und es erzielt eine Beschleunigung von 15-20 %, indem es einen einzelnen unvorhersehbaren Zweig auf einem switch mit vorhersagbareren Verzweigungen pro Opcode; Jeder Opcode hat sein eigenes Muster dessen, welche Opcodes ihm folgen, sodass Verzweigungen pro Opcode eine Verzweigungsvorhersage pro Opcode bedeuten, was viel zuverlässiger ist). Es ist alles hinter Makros versteckt, also funktioniert es auch, wenn es auf Nicht-GCC-Compilern kompiliert wird (über eine Standard-Endlosschleife + switch Konstrukt), es ist nur langsamer.

    – ShadowRanger

    8. September um 19:46 Uhr

Fabels Benutzer-Avatar
Fabel

Ich kenne das Gefühl, wenn jeder sagt, es sollte nicht getan werden; es gerade hat getan werden. Bei Verwendung von GNU C &&the_label; um die Adresse eines Etiketts zu übernehmen. (https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html) Die Syntax, die Sie erraten haben, goto *ptr auf einen void*ist eigentlich das, was GNU C verwendet.

Oder wenn Sie aus irgendeinem Grund die Inline-Assemblierung verwenden möchten, gehen Sie wie folgt vor GNU-C asm goto

// unsafe: this needs to use  asm goto so the compiler knows
// execution might not come out the other side
#define unsafe_jumpto(a) asm("jmp *%0"::"r"(a):)

// target pointer, possible targets
#define jumpto(a, ...) asm goto("jmp *%0" : : "r"(a) : : __VA_ARGS__)

int main (void)
{
  int i=1;
  void* the_label_pointer;

  the_label:
  the_label_pointer = &&the_label;

label2:

  if( i-- )
    jumpto(the_label_pointer, the_label, label2, label3);

label3:
  return 0;
}

Die Liste der Labels muss jeden möglichen Wert für enthalten the_label_pointer.

Die Makroerweiterung wird so etwas wie sein

asm goto("jmp *%0" : : "ri"(the_label_pointer) : : the_label, label2, label3);

Dies kompiliert mit gcc 4.5 und höher und mit dem neuesten Clang, der gerade kam asm goto Unterstützung einige Zeit nach Clang 8.0. https://godbolt.org/z/BzhckE. Das resultierende asm sieht so für GCC9.1 aus, das die “Schleife” von wegoptimiert hat i=i / i-- und einfach die setzen the_label nach das jumpto. Es läuft also immer noch genau einmal, wie in der C-Quelle.

# gcc9.1 -O3 -fpie
main:
    leaq    .L2(%rip), %rax     # ptr = &&label
    jmp *%rax                     # from inline asm
.L2:
    xorl    %eax, %eax          # return 0
    ret

Aber clang hat diese Optimierung nicht durchgeführt und hat immer noch die Schleife:

# clang -O3 -fpie
main:
    movl    $1, %eax
    leaq    .Ltmp1(%rip), %rcx
.Ltmp1:                                 # Block address taken
    subl    $1, %eax
    jb      .LBB0_4                  # jump over the JMP if i was < 1 (unsigned) before SUB.  i.e. skip the backwards jump if i wrapped
    jmpq    *%rcx                   # from inline asm
.LBB0_4:
    xorl    %eax, %eax              # return 0
    retq

Der Label-Adressoperator && funktioniert nur mit gcc. Und natürlich muss das Jumpto-Assembly-Makro speziell für jeden Prozessor implementiert werden (dieser funktioniert sowohl mit 32- als auch mit 64-Bit-x86).

Denken Sie auch daran, dass (ohne asm goto) gäbe es keine Garantie dafür, dass der Zustand des Stapels an zwei verschiedenen Stellen in derselben Funktion gleich ist. Und zumindest bei aktivierter Optimierung ist es möglich, dass der Compiler davon ausgeht, dass einige Register an der Stelle nach dem Label einen Wert enthalten. Diese Art von Dingen kann leicht vermasselt werden und macht dann verrückten Scheiß, den der Compiler nicht erwartet. Achten Sie darauf, den kompilierten Code Korrektur zu lesen.

Das sind die Gründe asm goto ist notwendig, um es sicher zu machen, indem Sie den Compiler wissen lassen, wohin Sie springen werden / könnten, um konsistente Code-Generierung für den Sprung und das Ziel zu erhalten.

  • Kannst du nicht einfach lea eax, label; mov label_ptr, eax (Intel-Syntax), um den Zeiger in einer Variablen zu speichern?

    – Kalmarius

    16. Oktober 2014 um 8:45 Uhr


  • Es besteht kein Zweifel, dass es in der Baugruppe implementiert werden kann (was in diesem Fall vielleicht besser in Betracht gezogen werden könnte). Ein Vorteil der Implementierung in C besteht darin, dass der Compiler einige Optimierungen vornimmt.

    – Fabel

    17. Oktober 2014 um 16:51 Uhr

  • Eine der besten Antworten hier, vielen Dank, hat mir bei einem Reverse-Engineering-Projekt geholfen.

    – kungfooman

    30. März 2016 um 5:03 Uhr

  • Ich denke, dies ist tatsächlich eine sehr nützliche Funktion für Grenzfälle, in denen Sie keine unendliche Rekursion durchführen können, da dies Ihren Stackframe sprengen würde und Sie den Kontext verfolgen müssen, ohne jedes Mal zu verzweigen, bevor Sie springen. Schade, dass es nur in gcc implementiert ist 🙁

    – Lichtungen

    15. Juni um 16:23 Uhr

  • @glades Dasselbe kann mit einer switch-Anweisung erreicht werden, da die Labels sowieso zu einem vordefinierten Satz gehören müssen. Wenn Sie alle Funktionen in einem Schalter platzieren, können Sie jedes Label in perfekt portierbarem C aufrufen und anspringen. Ja, Case-Labels können überall im Code stehen, sogar innerhalb von Blöcken von was auch immer. (Dies gilt für die von Peter Cordes neu geschriebene Antwort. Meine ursprüngliche Antwort erlaubte das Springen zwischen Code in verschiedenen Objektdateien auf weniger eingeschränkte und weniger sichere Weise.)

    – Fabel

    16. Juni um 18:16 Uhr

Sie können etwas Ähnliches mit setjmp/longjmp machen.

int main (void)
{
    jmp_buf buf;
    int i=1;

    // this acts sort of like a dynamic label
    setjmp(buf);

    if( i-- )
        // and this effectively does a goto to the dynamic label
        longjmp(buf, 1);

    return 0;
}

  • Nur eine Warnung, dass setjmp/longjmp langsam sein können, da sie viel mehr als nur den Programmzähler speichern und wiederherstellen.

    – RickNZ

    22. November 2009 um 7:00 Uhr

  • Das geht nicht: je nachdem ob i wird in einem Register gespeichert bzw auf dem Stapelsein ursprünglicher Wert (1) wird wiederhergestellt von longjmp() oder nicht, wodurch möglicherweise eine Endlosschleife verursacht wird.

    – chqrlie

    9. August 2019 um 6:29 Uhr

AnT steht mit Russlands Benutzer-Avatar
AnT steht zu Russland

In der sehr sehr sehr alten Version der C-Sprache (denken Sie an die Zeit, als Dinosaurier die Erde durchstreiften), bekannt als “C Reference Manual”-Version (die sich auf a dokumentieren geschrieben von Dennis Ritchie), hatten Labels formal den Typ “array of int” (seltsam, aber wahr), was bedeutet, dass Sie ein deklarieren konnten int * Variable

int *target;

und weisen Sie dieser Variablen die Adresse des Labels zu

target = label; /* where `label` is some label */

Später könnten Sie diese Variable als Operand von verwenden goto Aussage

goto target; /* jumps to label `label` */

In ANSI C wurde diese Funktion jedoch verworfen. Im modernen Standard-C können Sie keine Adresse eines Labels nehmen und Sie können nicht “parametrisiert” tun. goto. Dieses Verhalten soll mit simuliert werden switch Anweisungen, Zeiger auf Funktionen und andere Methoden usw. Tatsächlich sagte sogar “C Reference Manual” selbst, dass “Label-Variablen im Allgemeinen eine schlechte Idee sind; die switch-Anweisung macht sie fast immer unnötig” (siehe “14.4 Etiketten”).

Gemäß dem C99-Standard, § 6.8.6, ist die Syntax für a goto ist:

    goto identifier ;

Selbst wenn Sie also die Adresse eines Labels übernehmen könnten, könnten Sie es nicht mit goto verwenden.

Du könntest a kombinieren goto mit einer switchwas wie eine berechnete ist gotofür einen ähnlichen Effekt:

int foo() {
    static int i=0;
    return i++;
}

int main(void) {
    enum {
        skip=-1,
        run,
        jump,
        scamper
    } label = skip; 

#define STATE(lbl) case lbl: puts(#lbl); break
    computeGoto:
    switch (label) {
    case skip: break;
        STATE(run);
        STATE(jump);
        STATE(scamper);
    default:
        printf("Unknown state: %d\n", label);
        exit(0);
    }
#undef STATE
    label = foo();
    goto computeGoto;
}

Wenn Sie dies für etwas anderes als einen verschleierten C-Wettbewerb verwenden, werde ich Sie jagen und Ihnen wehtun.

  • Was ist der Unterschied zwischen puts(#lbl) und puts(lbl)?

    – Ahmed

    22. November 2009 um 7:31 Uhr

  • Das # ist der Stringisierungsoperator des Präprozessors (en.wikipedia.org/wiki/C_preprocessor#Quoting_macro_arguments). Es wandelt Bezeichner in Zeichenfolgen um. puts(lbl) wird nicht kompiliert, weil lbl ist kein char *.

    – aussen

    22. November 2009 um 7:54 Uhr

  • Vielmehr wird es mit Warnungen kompiliert und abstürzt, wenn Sie es ausführen.

    – aussen

    23. November 2009 um 1:33 Uhr

  • +1 für böses Denken und die Verwendung von Makros über die Pflicht hinaus.

    – EvilTeach

    2. April 2010 um 22:20 Uhr


Das switch ... case Aussage ist im Wesentlichen a berechnet goto. Ein gutes Beispiel dafür, wie es funktioniert, ist der bizarre Hack, bekannt als Duffs Gerät:

send(to, from, count)
register short *to, *from;
register count;
{
    register n=(count+7)/8;
    switch(count%8){
    case 0: do{ *to = *from++;
    case 7:     *to = *from++;
    case 6:     *to = *from++;
    case 5:     *to = *from++;
    case 4:     *to = *from++;
    case 3:     *to = *from++;
    case 2:     *to = *from++;
    case 1:     *to = *from++;
        }while(--n>0);
    }
}

Du kannst nicht goto von einem beliebigen Ort mit dieser Technik, aber Sie können Ihre gesamte Funktion in a einschließen switch Anweisung basierend auf einer Variablen, dann setzen Sie diese Variable, die angibt, wohin Sie gehen möchten, und goto diese switch-Anweisung.

int main () {
  int label = 0;
  dispatch: switch (label) {
  case 0:
    label = some_computation();
    goto dispatch;
  case 1:
    label = another_computation();
    goto dispatch;
  case 2:
    return 0;
  }
}

Wenn Sie dies häufig tun, möchten Sie natürlich einige Makros schreiben, um es einzuschließen.

Diese Technik kann zusammen mit einigen praktischen Makros sogar zur Implementierung verwendet werden Koroutinen in C.

  • Was ist der Unterschied zwischen puts(#lbl) und puts(lbl)?

    – Ahmed

    22. November 2009 um 7:31 Uhr

  • Das # ist der Stringisierungsoperator des Präprozessors (en.wikipedia.org/wiki/C_preprocessor#Quoting_macro_arguments). Es wandelt Bezeichner in Zeichenfolgen um. puts(lbl) wird nicht kompiliert, weil lbl ist kein char *.

    – aussen

    22. November 2009 um 7:54 Uhr

  • Vielmehr wird es mit Warnungen kompiliert und abstürzt, wenn Sie es ausführen.

    – aussen

    23. November 2009 um 1:33 Uhr

  • +1 für böses Denken und die Verwendung von Makros über die Pflicht hinaus.

    – EvilTeach

    2. April 2010 um 22:20 Uhr


Benutzeravatar von Kip Ingram
Kip Ingram

Ich werde anmerken, dass die hier beschriebene Funktion (einschließlich && in gcc) IDEAL für die Implementierung eines Forth-Sprachinterpreters in C ist. Das bläst alle “Tu das nicht”-Argumente aus dem Wasser – die Passung zwischen dieser Funktionalität und dem Weg Forths innere Dolmetscherarbeit ist zu gut, um sie zu ignorieren.

1415610cookie-checkIst es möglich, die Adresse eines Labels in einer Variablen zu speichern und mit goto dorthin zu springen?

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

Privacy policy