Teilen Sie gcc mit, dass ein Funktionsaufruf nicht zurückgegeben wird

Lesezeit: 10 Minuten

Benutzer-Avatar
Tom

ich benutze C99 unter GCC.

Ich habe eine Funktion deklariert static inline in einem Header, den ich nicht ändern kann.

Die Funktion kehrt nie zurück, ist aber nicht markiert __attribute__((noreturn)).

Wie kann ich die Funktion so aufrufen, dass der Compiler nicht zurückkehrt?

Ich rufe es von meiner eigenen Noreturn-Funktion aus auf und möchte teilweise die Warnung “Norreturn-Funktion gibt zurück” unterdrücken, aber auch dem Optimierer helfen usw.

Ich habe versucht, eine Deklaration mit dem Attribut einzuschließen, erhalte jedoch eine Warnung über die wiederholte Deklaration.

Ich habe versucht, einen Funktionszeiger zu erstellen und das Attribut darauf anzuwenden, aber es besagt, dass das Funktionsattribut nicht auf eine spitze Funktion angewendet werden kann.

  • Wenn ich das richtig verstehe, willst du eine Funktionsausgabe ignorieren? Nur weil eine Funktion einen Wert zurückgibt, heißt das nicht, dass jede Funktion, die diese Funktion aufruft, auch den Wert zurückgeben muss? Irgendwelche Codebeispiele?

    – Benutzer3791372

    20. August 2014 um 15:35 Uhr


  • Können Sie zeigen, wie Ihre noreturn-Funktion definiert ist?

    – R Sahu

    20. August 2014 um 15:35 Uhr

  • Dies ist großartig, um nicht relevante Warnungen bei Funktionen zu beseitigen, die beenden oder Ausnahmen auslösen. Sie müssen nicht irgendwie etwas zurückgeben.

    – Audrius Meškauskas

    18. November 2021 um 9:28 Uhr


Benutzer-Avatar
Anton

Von der Funktion Sie definiert ist und die externe Funktion aufruft, fügen Sie einen Aufruf hinzu __builtin_unreachable die zumindest eingebaut ist GCC und Klirren Compiler und ist markiert noreturn. Tatsächlich macht diese Funktion nichts anderes und sollte nicht aufgerufen werden. Es ist nur hier, damit der Compiler darauf schließen kann, dass die Programmausführung an dieser Stelle stoppt.

static inline external_function() // lacks the noreturn attribute
{ /* does not return */ }

__attribute__((noreturn)) void your_function() {
    external_function();     // the compiler thinks execution may continue ...
    __builtin_unreachable(); // ... and now it knows it won't go beyond here
}

Bearbeiten: Nur um ein paar Punkte zu verdeutlichen, die in den Kommentaren angesprochen wurden, und allgemein ein wenig Kontext zu geben:

  • Eine Funktion hat nur zwei Möglichkeiten, nicht zurückzukehren: eine Endlosschleife oder den üblichen Kontrollfluss kurzschließen (z. B. eine Ausnahme auslösen, aus der Funktion springen, den Prozess beenden usw.).
  • In manchen Fällen der Compiler kann in der Lage sein, durch statische Analyse abzuleiten und zu beweisen, dass eine Funktion nicht zurückkehrt. Sogar theoretisch ist das so nicht immer möglichund da wollen wir Compiler sein schnell nur offensichtliche/einfache Fälle werden erkannt.
  • __attribute__((noreturn)) ist eine Anmerkung (wie const) Dies ist eine Möglichkeit für den Programmierer, den Compiler darüber zu informieren, dass er absolut sicher ist, dass eine Funktion nicht zurückgegeben wird. Im Anschluss an die vertraue, aber überprüfe Grundsätzlich versucht der Compiler zu beweisen, dass die Funktion tatsächlich nicht zurückkehrt. Es kann dann einen Fehler ausgeben, wenn es beweist, dass die Funktion zurückkehren kann, oder eine Warnung, wenn es nicht beweisen konnte, ob die Funktion zurückkehrt oder nicht.
  • __builtin_unreachable hat undefiniertes Verhalten weil es nicht dazu bestimmt ist, aufgerufen zu werden. Es soll nur die statische Analyse des Compilers unterstützen. Tatsächlich weiß der Compiler, dass diese Funktion nicht zurückkehrt, daher ist jeder folgende Code nachweislich nicht erreichbar (außer durch einen Sprung).

Sobald der Compiler (entweder selbst oder mit Hilfe des Programmierers) festgestellt hat, dass ein bestimmter Code nicht erreichbar ist, kann er diese Informationen verwenden, um Optimierungen wie die folgenden durchzuführen:

  • Entfernen Sie den Boilerplate-Code, der verwendet wird, um von einer Funktion zu ihrem Aufrufer zurückzukehren, wenn die Funktion nie zurückkehrt
  • Verbreiten Sie die Unerreichbarkeit Informationen, dh wenn der einzige Ausführungspfad zu einem Codepunkt durch unerreichbaren Code führt, dann ist dieser Punkt ebenfalls unerreichbar. Beispiele:
    • wenn eine Funktion nicht zurückkehrt, irgendein Code folgende sein Anruf und nicht erreichbar durch Sprünge ist auch nicht erreichbar. Beispiel: Code folgt __builtin_unreachable() ist unerreichbar.
    • Insbesondere wenn der einzige Weg zur Rückkehr einer Funktion über unerreichbaren Code führt, kann die Funktion markiert werden noreturn. Dafür passiert es your_function.
    • Speicherorte / Variablen, die nur in unerreichbarem Code verwendet werden, werden nicht benötigt, daher ist keine Einstellung / Berechnung des Inhalts solcher Daten erforderlich.
    • irgendwelche Berechnungen, die wahrscheinlich ist (1) unnötig (vorheriger Aufzählungspunkt) und (2) hat keine Nebenwirkungen (wie z pure Funktionen) können entfernt werden.

Illustration:

  • Der Aufruf an external_function kann nicht entfernt werden, da es Nebenwirkungen haben könnte. Tatsächlich hat es wahrscheinlich zumindest den Nebeneffekt, dass der Prozess beendet wird!
  • Die Rücklaufkesselplatte von your_function dürfen entfernt werden

Hier ist ein weiteres Beispiel, das zeigt, wie Code Vor der unerreichbare Punkt kann entfernt werden

int compute(int) __attribute((pure)) { return /* expensive compute */ }
if(condition) {
    int x = compute(input); // (1) no side effect => keep if x is used
                            // (8) x is not used  => remove
    printf("hello ");       // (2) reachable + side effect => keep
    your_function();        // (3) reachable + side effect => keep
                            // (4) unreachable beyond this point
    printf("word!\n");      // (5) unreachable => remove
    printf("%d\n", x);      // (6) unreachable => remove
                            // (7) mark 'x' as unused
} else {
                            // follows unreachable code, but can jump here
                            // from reachable code, so this is reachable
   do_stuff();              // keep
}

  • Gibt es keinen Unterschied zw __builtin_unreachable() und nicht zurückkehren? Im Speziellen, __builtin_unreachable teilt dem Compiler mit, dass der Speicherort nicht erreichbar ist – er könnte also davon ausgehen, dass alles, was nicht aus einem Sprung von oben kommt, ebenfalls nicht erreichbar ist. In Ihrem Beispiel der Aufruf an external_function() ist nicht bedingt. Daher könnte man davon ausgehen, dass es auch nicht erreichbar ist?

    – Netzwerk

    20. August 2014 um 16:45 Uhr

  • @intekngt: du liegst wahrscheinlich falsch. Lesen mehr über __builtin_unreachable

    – Basile Starynkevitch

    20. August 2014 um 16:56 Uhr

  • @inetknght: Aus Sicht des Verhaltens gibt es nur zwei Möglichkeiten, die Wiederholung nicht auszuführen: Endlosschleife oder Ausführung abbrechen. builtin_unreachable tut eines dieser Dinge (ich denke, welches ist undefiniert) und teilt es dem Compiler mit für den Fall, dass es nicht schlau genug ist, dies anhand der statischen Analyse herauszufinden. In jedem Fall kann der Compiler, wenn er einen nicht erreichbaren Ort findet, den Code davor optimieren, wie Sie sagten, nur wenn es beweisen kann, dass der Code keine Nebenwirkungen hatwie ein rein Funktion. In unserem Fall, external_function ist nicht rein und könnte eine Datei oder ähnliches schreiben und kann nicht optimiert werden.

    – Anton

    20. August 2014 um 16:59 Uhr

  • Ich habe bearbeitet, um etwas ausführlicher zu erklären, wie Compiler unerreichbaren Code ausnutzen können, hoffe, es macht Sinn 🙂

    – Anton

    21. August 2014 um 10:08 Uhr

  • Es gibt eine dritte Möglichkeit, nicht zurückzukehren: Exceptions (oder longjmp). Zumindest das Verlassen einer Funktion über eine nicht abgefangene Ausnahme wird vom gcc-Attribut nicht als Rückkehr angesehen. Das hat aber keinen Einfluss auf die Antwort.

    – Marc Glisse

    21. August 2014 um 13:27 Uhr


Benutzer-Avatar
Basile Starynkevitch

Mehrere Lösungen:

Neudeklaration Ihrer Funktion mit der __attribute__

Sie sollten versuchen, diese Funktion in ihrem Header durch Hinzufügen zu ändern __attribute__((noreturn)) dazu.

Sie können einige Funktionen mit einem neuen Attribut neu deklarieren, wie dieser dumme Test zeigt (Hinzufügen eines Attributs zu fopen) :

 #include <stdio.h>

 extern FILE *fopen (const char *__restrict __filename,
            const char *__restrict __modes)
   __attribute__ ((warning ("fopen is used")));

 void
 show_map_without_care (void)
 {
   FILE *f = fopen ("/proc/self/maps", "r");
   do
     {
       char lin[64];
       fgets (lin, sizeof (lin), f);
       fputs (lin, stdout);
     }
   while (!feof (f));
   fclose (f);
 }

mit einem Makro überschreiben

Endlich könnten Sie ein Makro wie definieren

#define func(A) {func(A); __builtin_unreachable();}

(Dies nutzt die Tatsache, dass der Makroname innerhalb eines Makros nicht makroerweitert wird).

Wenn du nie wiederkommst func als zurückgebend deklariert, z int Sie verwenden a Aussage Ausdruck wie

#define func(A) ({func(A); __builtin_unreachable(); (int)0; })

Makrobasierte Lösungen wie oben funktionieren nicht immer, zB wenn func wird als Funktionszeiger übergeben, oder einfach, wenn ein Typ codiert (func)(1) das ist legal, aber hässlich.


Neudeklarieren einer Statik inline mit der noreturn Attribut

Und folgendes Beispiel:

 // file ex.c
 // declare exit without any standard header
 void exit (int);

 // define myexit as a static inline
 static inline void
 myexit (int c)
 {
   exit (c);
 }

 // redeclare it as notreturn
 static inline void myexit (int c) __attribute__ ((noreturn));

 int
 foo (int *p)
 {
   if (!p)
     myexit (1);
   if (p)
     return *p + 2;
   return 0;
 }

beim Kompilieren mit GCC 4.9 (von Debian/Sid/x86-64) als gcc -S -fverbose-asm -O2 ex.c) ergibt eine Assembly-Datei mit der erwarteten Optimierung:

         .type   foo, @function
 foo:
 .LFB1:
    .cfi_startproc
    testq   %rdi, %rdi      # p
    je      .L5     #,
    movl    (%rdi), %eax    # *p_2(D), *p_2(D)
    addl    $2, %eax        #, D.1768
    ret
.L5:
    pushq   %rax    #
    .cfi_def_cfa_offset 16
    movb    $1, %dil        #,
    call    exit    #
    .cfi_endproc
 .LFE1:
    .size   foo, .-foo

Du könntest damit spielen #pragma GCC-Diagnose um eine Warnung selektiv zu deaktivieren.


Anpassen GCC mit SCHMELZEN

Schließlich könnten Sie Ihre letzten anpassen gcc Verwendung der SCHMELZEN plugin und codieren Sie Ihre einfache Erweiterung (in der SCHMELZEN domänenspezifische Sprache), um das Attribut hinzuzufügen noreturn wenn Sie auf die gewünschte Funktion stoßen. Es wird wahrscheinlich ein Dutzend MELT-Linien verwenden register_finish_decl_first und eine Übereinstimmung mit dem Funktionsnamen.

Da ich der Hauptautor von bin SCHMELZEN (freie Software GPLv3+) Ich könnte das vielleicht sogar für Sie codieren, wenn Sie fragen, zB hier oder vorzugsweise auf [email protected]; Geben Sie den konkreten Namen Ihrer nie wiederkehrenden Funktion an.

Wahrscheinlich sieht der MELT-Code so aus:

  ;;file your_melt_mode.melt
  (module_is_gpl_compatible "GPLv3+")
  (defun my_finish_decl (decl)
     (let ( (tdecl (unbox :tree decl))
       )
     (match tdecl
        (?(tree_function_decl_named
            ?(tree_identifier ?(cstring_same "your_function_name")))
          ;;; code to add the noreturn attribute
          ;;; ....
        ))))
  (register_finish_decl_first my_finish_decl)

Der echte MELT-Code ist etwas komplexer. Sie wollen definieren your_adding_attr_mode dort. Fragen Sie mich nach mehr.

Sobald Sie Ihre MELT-Erweiterung codiert haben your_melt_mode.melt für Ihre Bedürfnisse (und kompilierte diese MELT-Erweiterung in your_melt_mode.quicklybuilt.so wie dokumentiert in den MELT-Tutorials) kompilieren Sie Ihren Code mit

  gcc -fplugin=melt \
      -fplugin-arg-melt-extra=your_melt_mode.quicklybuilt \
      -fplugin-arg-melt-mode=your_adding_attr_mode \
      -O2 -I/your/include -c yourfile.c

Mit anderen Worten, Sie fügen nur ein paar hinzu -fplugin-* Flaggen zu Ihnen CFLAGS in deiner Makefile !

Übrigens, ich codiere gerade im MELT-Monitor (auf github: https://github.com/bstarynk/melt-monitor …, Datei meltmom-process.melt etwas ganz ähnliches.

Mit einer MELT-Erweiterung erhalten Sie keine zusätzliche Warnung, da die MELT-Erweiterung den internen GCC-AST (eine GCC Baum) der deklarierten Funktion on the fly!

Das Anpassen von GCC mit MELT ist wahrscheinlich die kugelsicherste Lösung, da es das GCC-interne AST modifiziert. Natürlich ist es wahrscheinlich die teuerste Lösung (und es ist GCC-spezifisch und erfordert möglicherweise -kleine- Änderungen, wenn GCC weiterentwickelt wird, z. B. wenn die nächste Version von GCC verwendet wird), aber wie ich versuche zu zeigen, ist es ziemlich einfach dein Fall.

PS. 2019 ist GCC MELT ein aufgegebenes Projekt. Wenn Sie GCC anpassen möchten (für eine neuere Version von GCC, zB GCC 7, 8 oder 9), müssen Sie Ihre eigene schreiben GCC-Plugin in C++.

  • Danke Basile für die drei Lösungen, 1) Wie ich in der Frage erwähnt habe, kann ich den Header nicht ändern. 2) Ich finde die Makrolösung hässlich, aber ich könnte einfach __builtin_unreachable() direkt nach der Aufrufseite hinzufügen; Ich bin in der Lage, den aufrufenden Code zu ändern. Wie ich auch in der Frage erwähnt habe, erzeugt die Neudeklaration der Funktion mit dem Attribut eine Warnung „redundante Neudeklaration“. Gibt es eine Möglichkeit, das Attribut hinzuzufügen, ohne diese Warnung auszulösen?

    – Tom

    20. August 2014 um 16:00 Uhr


  • Mit einer MELT-Erweiterung werden Sie sicherlich keine Warnungen erhalten, da die Deklaration von your_function_name wird modifiziert im Flug wenn GCC Ihren heiligen und unantastbaren Header analysiert ….

    – Basile Starynkevitch

    20. August 2014 um 16:32 Uhr


1364740cookie-checkTeilen Sie gcc mit, dass ein Funktionsaufruf nicht zurückgegeben wird

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

Privacy policy