In letzter Zeit habe ich versucht, mehr Open-Source-C-Code zu lesen. Ein gängiges Muster, das ich in meinen Hobbyprojekten übernommen habe, ist wie folgt.
In meinen C-Dateien habe ich Funktionen, die entweder statisch oder exportiert sind. Nur exportierte Funktionen werden in eine Header-Datei gestellt. Als statische globale Variablen werden auch globale Variablen verwendet, die nur im Rahmen eines Objekts verwendet werden.
Meine Frage betrifft den Nutzen und die Motivation des Habens static inline
Funktionen in Header-Dateien. Von dem, was ich online gelesen habe, verwende ich das nicht static
Das Schlüsselwort verursacht einen Mehrfachdefinitionsfehler, und das ist der Grund dafür, dass die Funktion nicht einfach als gerecht definiert wird inline
.
Bedeutet dies jedoch, dass diese Funktion für andere Objekte zur Verwendung exportiert wird? Wenn ja, warum dann nicht einfach diese Funktion in der C-Datei definieren und über die Header-Datei exportieren? Wenn nicht, warum diese in die Header-Datei einfügen, anstatt sie nur in der C-Datei zu haben?
Gibt es einen Grund für diesen Codierungsstil? Was vermisse ich?
Ein solches Beispiel kann in der Git-Codebasis im Inneren gefunden werden hashmap.h
:
/*
* Converts a cryptographic hash (e.g. SHA-1) into an int-sized hash code
* for use in hash tables. Cryptographic hashes are supposed to have
* uniform distribution, so in contrast to `memhash()`, this just copies
* the first `sizeof(int)` bytes without shuffling any bits. Note that
* the results will be different on big-endian and little-endian
* platforms, so they should not be stored or transferred over the net.
*/
static inline unsigned int sha1hash(const unsigned char *sha1)
{
/*
* Equivalent to 'return *(unsigned int *)sha1;', but safe on
* platforms that don't support unaligned reads.
*/
unsigned int hash;
memcpy(&hash, sha1, sizeof(hash));
return hash;
}
EIN static inline
Funktion ist in der Praxis wahrscheinlich (aber nicht sicher) zu sein eingebettet von einem guten optimierenden Compiler (zB by GCC wenn es gegeben ist -O2
) an den meisten seiner Aufrufseiten.
Es wird in einer Header-Datei definiert, weil es dann bei den meisten Aufrufseiten (vielleicht allen) inline sein könnte. Wenn es nur so wäre erklärt (und einfach “exportiert”), ist es unwahrscheinlich, dass das Inlining stattfindet (außer wenn Sie kompilieren und verlinken mit Link-Time-Optimierungenauch bekannt als LTO, zB kompilieren und verknüpfen mit gcc -flto -O2
und das nimmt zu viel die Bauzeit).
In der Praxis muss der Compiler den Rumpf einer Funktion kennen, um sie einbetten zu können. Ein geeigneter Ort ist es also, es in einer gemeinsamen Header-Datei zu definieren (andernfalls könnte es nur in dieselbe Übersetzungseinheit eingebettet werden, die es definiert, es sei denn, Sie aktivieren LTO), sodass jede Übersetzungseinheit den Körper dieser inlinierbaren Funktion kennt.
Es ist deklariert static
um mehrfache Definitionen (zur Verbindungszeit) zu vermeiden, falls der Compiler sie nicht eingefügt hat (z. B. wenn Sie ihre Adresse verwenden).
In der Praxis würde ich in C99- oder C11-Code (außer bei LTO, das ich selten verwende) immer die kurzen Funktionen einfügen, als die ich inliniert werden möchte static inline
Definitionen in gemeinsamen Header-Dateien.
Achten Sie darauf, zu verstehen, wie und wann die C-Präprozessor funktioniert. Beachten Sie, dass Sie im Prinzip (aber es wäre eine sehr schlechte Übung und ein widerlicher Stil) vermeiden könnten, einige zu definieren static inline
Funktion in einer gemeinsamen Header-Datei und kopieren und fügen Sie stattdessen ihre identische Definition in mehreren ein .c
Dateien. (Allerdings könnte das sinnvoll sein generiert .c
Dateien, zB wenn Sie eine entwerfen Compiler, der C-Code ausgibt).
FYI LTO wird von neueren GCC-Compilern praktisch implementiert, indem einige interne Compiler-Darstellungen (einige GIMPLE) in Objektdateien und Wiederholen einiger “Kompilierungs” -Schritte – Verwendung der lto1
Frontend – zur “Link”-Zeit. In der Praxis wird das gesamte Programm fast “zweimal” kompiliert.
(Eigentlich habe ich mich immer gefragt, warum das C-Standardisierungsgremium nicht stattdessen alles explizit entschieden hat inline
Funktionen sind statisch)
Als Referenz: stackoverflow.com/help/mcve
– Fabelhaft
14. Dezember 2017 um 18:35 Uhr
Meinst du mit “exportiert” “extern”? Meinst du vielleicht konkreter “deklariert mit an
extern
Speicherklassenbezeichner”?– Johannes Bollinger
14. Dezember 2017 um 18:36 Uhr
Seien Sie sich auf jeden Fall bewusst, dass es bereits solche gibt viele Fragen (mit Antworten) zu C-Inline-Funktionen hier auf SO. Sind Sie sicher, dass Ihre sinnvoll getrennt ist?
– Johannes Bollinger
14. Dezember 2017 um 18:40 Uhr
Mit “exportiert” meine ich eine Funktion, die von einer anderen Objektdatei verwendet werden kann. Sie könnten Funktionen sein, die in einer C-Datei definiert und in einer Header-Datei deklariert sind und nicht unbedingt das Schlüsselwort “extern” verwenden. Ich habe eine Bearbeitung vorgenommen. Ich habe einige Fragen zu “Static Inline” gesehen, aber sie schienen meine Frage nicht zu beantworten. Meine Frage ist ein Versuch zu verstehen, warum sich das obige Code-Snippet in einer Header-Datei im Gegensatz zu einer C-Datei befindet. Ich glaube, ich habe auch ähnlichen Code im Linux-Kernel-Code gesehen.
– lernenlernenlernen
14. Dezember 2017 um 20:07 Uhr
Beachten Sie, dass es einen großen Unterschied zwischen der Definition von Ebene gibt
static
Funktionen in Header-Dateien (möglicherweise ein Fehler) undstatic inline
Funktionen (wahrscheinlich eine gute Idee, wenn die Funktionen inline-fähig sind). Wenn die Funktion zu groß oder zu komplex ist, um inline erstellt zu werden, erhalten Sie am Ende eine Kopie der Funktion in jeder Objektdatei (und Sie erhalten Warnungen, wenn die Funktion nicht verwendet wird, während Sie dies bei Inline-Funktionen nicht tun).– Jonathan Leffler
14. Dezember 2017 um 20:26 Uhr