Das __is_constexpr-Makro des Linux-Kernels

Lesezeit: 8 Minuten

Benutzeravatar von Acorn
Eichel

Wie funktioniert die __is_constexpr(x) Makro des Linux-Kernels funktionieren? Was ist seine Aufgabe? Wann wurde es eingeführt? Warum wurde es eingeführt?

/*
 * This returns a constant expression while determining if an argument is
 * a constant expression, most importantly without evaluating the argument.
 * Glory to Martin Uecker <[email protected]>
 */
#define __is_constexpr(x) \
        (sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))

Für ein Diskussion über verschiedene Ansätze Um das gleiche Problem zu lösen, lesen Sie stattdessen: Erkennung ganzzahliger Konstantenausdrücke in Makros

  • Ich glaube nicht, dass das allgemein bekannt ist sizeof(void) ist laut Norm nicht erlaubt… Die relevanten Passagen sind §6.5.3.4 Abs. 1 (void ist ein unvollständiger Typ) und §6.2.5 Abs. 19 (keine unvollständigen Typen zu sizeofwas offensichtlich sein sollte).

    – nemequ

    25. März 2018 um 21:54 Uhr

  • Sie haben nicht klargestellt, dass “Erklären Sie ein Makro, das auf einen ganzzahligen konstanten Ausdruck testet” Ihre eigene aktuelle Frage war. Ist das von praktischem Nutzen oder akademischer Kaugummi? Schöne Antwort auf deine eigene Frage.

    – Wetterfahne

    25. März 2018 um 21:55 Uhr


  • @WeatherVane: Danke! Dieses Makro wurde vor ein paar Tagen in der LKML als mögliche Lösung für ein Problem mit VLAs im Kernel diskutiert, daher ist es klar, dass es einen praktischen Nutzen hat – besonders, wenn es im Kernelbaum landet.

    – Eichel

    25. März 2018 um 22:29 Uhr

  • Ich bin da bei Linus. Und da dieses Makro auch Compiler-spezifisch ist, warum nicht ein eingebautes verwenden, das klar wäre?

    – zu ehrlich für diese Seite

    26. März 2018 um 0:15 Uhr


  • Ich bin mir nicht sicher, wie jemand die Frage unklar findet

    – MM

    27. März 2018 um 20:43 Uhr

Benutzeravatar von Acorn
Eichel

Die des Linux-Kernels __is_constexpr Makro

Einführung

Das __is_constexpr(x) Makro finden Sie im Linux-Kernel include/kernel/kernel.h:

/*
 * This returns a constant expression while determining if an argument is
 * a constant expression, most importantly without evaluating the argument.
 * Glory to Martin Uecker <[email protected]>
 */
#define __is_constexpr(x) \
        (sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))

Es wurde während des Zusammenführungsfensters für Linux Kernel v4.17 eingeführt, übertrage 3c8ba0d61d04 am 05.04.2018; obwohl die Diskussionen darüber einen Monat zuvor begannen.

Das Makro zeichnet sich dadurch aus, dass es subtile Details des C-Standards nutzt: die Bedingter Operatordie Regeln von zur Bestimmung des zurückgegebenen Typs (6.5.15.6) und die Definition von a Nullzeiger konstant (6.3.2.3.3).

Darüber hinaus stützt es sich auf sizeof(void) erlaubt (und anders als sizeof(int)), die ein GNU-C-Erweiterung.


Wie funktioniert es?

Der Körper des Makros ist:

(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))

Konzentrieren wir uns auf diesen Teil:

((void *)((long)(x) * 0l))

Beachten Sie das (long)(x) Besetzung ist beabsichtigt erlauben x Zeigertypen zu haben und Warnungen zu vermeiden u64 Typen auf 32-Bit-Plattformen. Dieses Detail ist jedoch für das Verständnis der Kernpunkte des Makros nicht wichtig.

Wenn x ist ein ganzzahliger konstanter Ausdruck (6.6.6), dann folgt daraus ((long)(x) * 0l) ist ein ganzzahliger konstanter Ausdruck von Wert 0. Deswegen, (void *)((long)(x) * 0l) ist ein Nullzeiger konstant (6.3.2.3.3):

Ein ganzzahliger konstanter Ausdruck mit dem Wert 0 oder ein solcher Ausdruck, der in einen Typ umgewandelt wird Leere *heißt a Nullzeiger konstant

Wenn x ist nicht ein ganzzahliger konstanter Ausdruckdann (void *)((long)(x) * 0l) ist kein Nullzeiger konstant, unabhängig von seinem Wert.

Wenn wir das wissen, können wir sehen, was danach passiert:

8 ? ((void *)((long)(x) * 0l)) : (int *)8

Hinweis: die zweite 8 wörtlich ist beabsichtigt um Compiler-Warnungen über das Erstellen von Zeigern auf nicht ausgerichtete Adressen zu vermeiden. Der Erste 8 wörtlich könnte einfach sein 1. Diese Details sind jedoch für das Verständnis der Kernpunkte des Makros nicht wichtig.

Der Schlüssel hier ist, dass die Bedingter Operator gibt a zurück Anderer Typ abhängig davon, ob einer der Operanden a ist Nullzeiger konstant (6.5.15.6):

[…] wenn ein Operand eine Nullzeigerkonstante ist, hat das Ergebnis den Typ des anderen Operanden; andernfalls ist ein Operand ein Zeiger auf Leere oder eine qualifizierte Version von Leerein diesem Fall ist der Ergebnistyp ein Zeiger auf eine entsprechend qualifizierte Version von Leere.

Also, wenn x war ein ganzzahliger konstanter Ausdruckdann ist der zweite Operand a Nullzeiger konstant und daher ist der Typ des Ausdrucks der Typ des dritten Operanden, der ein Zeiger auf ist int.

Andernfallsder zweite Operand ist ein Zeiger auf void und somit ist der Typ des Ausdrucks ein Zeiger auf void.

Daher haben wir am Ende zwei Möglichkeiten:

sizeof(int) == sizeof(*((int *) (NULL))) // if `x` was an integer constant expression
sizeof(int) == sizeof(*((void *)(....))) // otherwise

Laut dem GNU-C-Erweiterung, sizeof(void) == 1. Daher, wenn x war ein ganzzahliger konstanter Ausdruckist das Ergebnis des Makros 1; Andernfalls, 0.

Da wir außerdem nur auf Gleichheit zwei vergleichen sizeof Ausdrücke, das Ergebnis selbst ist ein anderes ganzzahliger konstanter Ausdruck (6.6.3, 6.6.6):

Konstante Ausdrücke dürfen keine Zuweisungs-, Inkrement-, Dekrement-, Funktionsaufruf- oder Kommaoperatoren enthalten, es sei denn, sie sind in einem Unterausdruck enthalten, der nicht ausgewertet wird.

Ein ganzzahliger konstanter Ausdruck muss einen ganzzahligen Typ haben und darf nur Operanden haben, die ganzzahlige Konstanten, Aufzählungskonstanten, Zeichenkonstanten, Größe von Ausdrücke, deren Ergebnisse ganzzahlige Konstanten sind, und Gleitkommakonstanten, die die unmittelbaren Operanden von Umwandlungen sind. Cast-Operatoren in einem ganzzahligen konstanten Ausdruck konvertieren nur arithmetische Typen in ganzzahlige Typen, außer als Teil eines Operanden für die Größe von Operator.

Zusammenfassend also die __is_constexpr(x) Makro gibt ein zurück ganzzahliger konstanter Ausdruck von Wert 1 wenn das Argument ein ganzzahliger konstanter Ausdruck ist. Andernfalls wird ein zurückgegeben ganzzahliger konstanter Ausdruck von Wert 0.


Warum wurde es eingeführt?

Das Makro ist entstanden während der Anstrengung alle zu entfernen Arrays mit variabler Länge (VLAs) aus dem Linux-Kernel.

Um dies zu erleichtern, war es wünschenswert, dies zu ermöglichen GCCs -Wvla Warnung Kernel-weit; sodass alle Instanzen von VLAs vom Compiler gekennzeichnet wurden.

Als die Warnung aktiviert war, stellte sich heraus, dass GCC viele Fälle meldete, in denen Arrays VLAs waren, was nicht beabsichtigt war. Zum Beispiel im fs/btrfs/tree-checker.c:

#define BTRFS_NAME_LEN 255
#define XATTR_NAME_MAX 255

char namebuf[max(BTRFS_NAME_LEN, XATTR_NAME_MAX)];

Das darf ein Entwickler erwarten max(BTRFS_NAME_LEN, XATTR_NAME_MAX) wurde beschlossen 255 und daher sollte es als Standard-Array (dh Nicht-VLA) behandelt werden. Dies hängt jedoch davon ab, was die max(x, y) Makro erweitert zu.

Das Hauptproblem besteht darin, dass GCC VLA-Code generiert, wenn die Größe des Arrays keine ist (ganzzahliger) konstanter Ausdruck wie von der C-Norm definiert. Zum Beispiel:

#define not_really_constexpr ((void)0, 100)

int a[not_really_constexpr];

Gemäß dem C90-Standard, ((void)0, 100) ist nicht a ständiger Ausdruck (6.6), aufgrund der Komma-Operator verwendet wird (6.6.3). In diesem Fall entscheidet sich GCC für die Ausgabe des VLA-Codes, selbst wenn es weiß, dass die Größe eine Kompilierzeitkonstante ist. Clang dagegen nicht.

Seit der max(x, y) macro im Kernel kein konstanter Ausdruck war, löste GCC die Warnungen aus und generierte VLA-Code, wo Kernel-Entwickler es nicht beabsichtigt hatten.

Daher haben einige Kernel-Entwickler versucht, alternative Versionen des zu entwickeln max und andere Makros, um die Warnungen und den VLA-Code zu vermeiden. Einige Versuche versuchten, Hebelwirkung zu erzielen GCCs __builtin_constant_p eingebautaber kein Ansatz funktionierte mit allen Versionen von GCC, die der Kernel zu diesem Zeitpunkt unterstützte (gcc >= 4.4).

Irgendwann Martin Uecker vorgeschlagen ein besonders cleverer Ansatz, der keine eingebauten Funktionen verwendet (sich inspirieren lassen aus glibcs ​​tgmath.h):

#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))

Während der Ansatz eine GCC-Erweiterung verwendet, es kam trotzdem gut an und wurde als Schlüsselidee hinter dem verwendet __is_constexpr(x) Makro, das nach einigen Iterationen mit anderen Entwicklern im Kernel auftauchte. Das Makro wurde dann verwendet, um das zu implementieren max Makro und andere Makros, die konstante Ausdrücke sein müssen, um zu vermeiden, dass GCC VLA-Code generiert.

  • Das Zitieren des Standards wird hier nicht wirklich helfen, da das Verhalten implementierungsdefiniertes und möglicherweise (ich bin nicht in der Stimmung zu verifizieren) undefiniertes Verhalten enthält. Es ist nur ein schlechter Programmierstil.

    – zu ehrlich für diese Seite

    26. März 2018 um 0:18 Uhr

  • @Olaf: Ich bin mir nicht sicher, ob ich deiner Logik folge. Implementierungsdefiniertes, unspezifiziertes und undefiniertes Verhalten sind Konzepte, die von den Standards selbst definiert und verwendet werden – also ist das Zitieren eines Standards eigentlich der richtige Weg, um festzustellen, ob etwas implementierungsdefiniert, undefiniert oder etwas anderes ist. Auf jeden Fall, sizeof(void) ist einfach kein gültiges C-Programm, da es gegen eine Einschränkung verstößt (6.2.5.19, 6.5.3.4.1). Was den Codierungsstil betrifft, so kann er nicht “schlecht” sein, da es in C89 keine bekannten “guten” portablen Möglichkeiten gibt, dies zu tun (wenn überhaupt!).

    – Eichel

    26. März 2018 um 3:27 Uhr

  • “Es gibt keine bekannten “guten” tragbaren Wege” – genau mein Punkt. Und wenn Sie sowieso bei IDB/UB bleiben (letzteres arbeitet gerade an einer bestimmten Implementierung): KISS

    – zu ehrlich für diese Seite

    26. März 2018 um 11:23 Uhr

  • @Olaf: Es gibt keine Möglichkeit, es einfach zu halten, wenn es keine andere Möglichkeit gibt. In jedem Fall geht es bei dieser Frage darum, das obige Makro als Ergänzung zur verknüpften Frage zu erklären – beziehen Sie sich also bitte stattdessen auf diese, um bessere Möglichkeiten zu diskutieren oder vorzuschlagen, dies zu erreichen.

    – Eichel

    26. März 2018 um 12:36 Uhr

1433400cookie-checkDas __is_constexpr-Makro des Linux-Kernels

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

Privacy policy