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.
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
}
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.
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++.
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