Warum enthält diese .c-Datei sich selbst?

Lesezeit: 8 Minuten

Benutzeravatar von Rodrigo Belli
Rodrigo Belli

Warum tut dies .c Datei #include selbst?

vsimple.c

#define USIZE 8
#include "vsimple.c"
#undef USIZE

#define USIZE 16
#include "vsimple.c"
#undef USIZE

#define USIZE 32
#include "vsimple.c"
#undef USIZE

#define USIZE 64
#include "vsimple.c"
#undef USIZE

  • Dadurch werden einige in Eclipse verwendete Indizierungssysteme beschädigt. Ich empfehle es nicht.

    – Stark

    13. Februar um 21:46 Uhr

  • Bitte geben Sie ein reproduzierbares Minimalbeispiel an. Es gibt Hinweise darauf, dass der betreffende Mechanismus nicht durch den gezeigten Code dargestellt wird. Das #ifndefs sind wichtig und wie USIZE verwendet wird, ist wichtig.

    – Yunnosch

    13. Februar um 21:54 Uhr

  • @stark Andererseits würde ich sowieso nicht empfehlen, Eclipse für die C-Entwicklung zu verwenden. Und das zu unterbrechen, ist nur ein Grund mehr, es zu vermeiden.

    – schwach

    14. Februar um 7:49 Uhr

  • @Stark Wenn es gültiges C ist, muss Eclipse eindeutig repariert werden.

    – Mark Morgan Lloyd

    14. Februar um 18:28 Uhr

  • Eine Möglichkeit, um zu sehen, wie dies funktioniert, besteht darin, die Datei vorzuverarbeiten und sich die endgültige Ausgabe anzusehen. Wie gcc -E foo.c. Dadurch wird bestätigt, dass es nicht unendlich rekursiv ist, dh dass sich diese Includes innerhalb von befinden #ifdef die du weggelassen hast. Der Inhalt der Ausgabe sollte Ihnen einen Hinweis darauf geben, was vor sich geht und worauf es ankommt.

    – Peter Cordes

    14. Februar um 21:45 Uhr

Benutzeravatar von chqrlie
chqrlie

Die Datei enthält sich selbst, sodass derselbe Quellcode verwendet werden kann, um 4 verschiedene Funktionssätze für bestimmte Werte des Makros zu generieren USIZE.

Das #include Direktiven sind eigentlich in einer eingeschlossen #ifndefwas die Rekursion auf eine einzige Ebene beschränkt:

#ifndef USIZE

// common definitions
...
//

#define VSENC vsenc
#define VSDEC vsdec

#define USIZE 8
#include "vsimple.c"
#undef USIZE

#define USIZE 16
#include "vsimple.c"
#undef USIZE

#define USIZE 32
#include "vsimple.c"
#undef USIZE

#define USIZE 64
#include "vsimple.c"
#undef USIZE

#else // defined(USIZE)

// macro expanded size specific functions using token pasting

...

#define uint_t TEMPLATE3(uint, USIZE, _t)

unsigned char *TEMPLATE2(VSENC, USIZE)(uint_t *__restrict in, size_t n, unsigned char *__restrict out) {
   ...
}

unsigned char *TEMPLATE2(VSDEC, USIZE)(unsigned char *__restrict ip, size_t n, uint_t *__restrict op) {
   ...
}

#endif

Die in diesem Modul definierten Funktionen sind

// vsencNN: compress array with n unsigned (NN bits in[n]) values to the buffer out. Return value = end of compressed output buffer out
unsigned char *vsenc8( unsigned char  *__restrict in, size_t n, unsigned char  *__restrict out);
unsigned char *vsenc16(unsigned short *__restrict in, size_t n, unsigned char  *__restrict out);
unsigned char *vsenc32(unsigned       *__restrict in, size_t n, unsigned char  *__restrict out);
unsigned char *vsenc64(uint64_t       *__restrict in, size_t n, unsigned char  *__restrict out);

// vsdecNN: decompress buffer into an array of n unsigned values. Return value = end of compressed input buffer in
unsigned char *vsdec8( unsigned char  *__restrict in, size_t n, unsigned char  *__restrict out);
unsigned char *vsdec16(unsigned char  *__restrict in, size_t n, unsigned short *__restrict out);
unsigned char *vsdec32(unsigned char  *__restrict in, size_t n, unsigned       *__restrict out);
unsigned char *vsdec64(unsigned char  *__restrict in, size_t n, uint64_t       *__restrict out);

Sie sind alle aus den beiden Funktionsdefinitionen in erweitert vsimple.c:

unsigned char *TEMPLATE2(VSENC, USIZE)(uint_t *__restrict in, size_t n, unsigned char *__restrict out) {
   ...
}

unsigned char *TEMPLATE2(VSDEC, USIZE)(unsigned char *__restrict ip, size_t n, uint_t *__restrict op) {
   ...
}

Das TEMPLATE2 und TEMPLATE3 Makros sind definiert in conf.h wie

#define TEMPLATE2_(_x_, _y_) _x_##_y_
#define TEMPLATE2(_x_, _y_) TEMPLATE2_(_x_,_y_)

#define TEMPLATE3_(_x_,_y_,_z_) _x_##_y_##_z_
#define TEMPLATE3(_x_,_y_,_z_) TEMPLATE3_(_x_, _y_, _z_)

Diese Makros sind klassische Präprozessorkonstruktionen, um Bezeichner per Token-Einfügen zu erstellen. TEMPLATE2 und TEMPLATE2_ werden häufiger genannt GLUE und XGLUE.

Die Funktionsvorlage beginnt wie folgt:

unsigned char *TEMPLATE2(VSENC, USIZE)(uint_t *__restrict in, size_t n, unsigned char *__restrict out) ...

Es wird bei der ersten rekursiven Inklusion mit expandiert USIZE definiert als 8 hinein:

unsigned char *vsenc8(uint8_t *__restrict in, size_t n, unsigned char *__restrict out) ...

Die zweite rekursive Inklusion, mit USIZE definiert als 16erweitert die Vorlage wie folgt:

unsigned char *vsenc16(uint16_t *__restrict in, size_t n, unsigned char *__restrict out) ...

und 2 weitere Einschlüsse definieren vsenc32 und vsenc64.

Diese Verwendung von vorverarbeitetem Quellcode ist bei separaten Dateien üblicher: eine für den instanziierenden Teil, der alle gemeinsamen Definitionen enthält, insbesondere die Makros, und eine separate Datei für die Code- und Datenvorlagen, die mehrmals mit verschiedenen Makrodefinitionen enthalten ist.

Ein gutes Beispiel ist die Generierung von Enums, Strings und Strukturen aus Arrays Atom und Operationscode Definitionen in QuickJS.

  • Ich verstehe den Mechanismus nicht, auf dem eine Definition von: unsigned char *TEMPLATE2(VSENC, USIZE)(uint_t *__restrict in, size_t n, unsigned char *__restrict out) {…}, der Compiler auf den 4 Optionen repliziert von USIZE

    – Rodrigo Belli

    13. Februar um 21:59 Uhr


  • @RodrigoBelli: Ich habe die Antwort mit weiteren Details zu den in diesem Paket verwendeten Funktionsvorlagen aktualisiert.

    – chqrlie

    13. Februar um 22:10 Uhr

  • Unter der Annahme, dass dies kein vom Compiler ignoriertes UB ist, kann diese Technik verwendet werden, um eine unbegrenzte Rekursion zu erstellen, die das fehlende Teil ist, wenn die meisten Leute den C-Präprozessor als Turing-unvollständig analysieren.

    – Jakob Manaker

    14. Februar um 7:35 Uhr

  • @JacobManaker: Interessanter Ansatz … Aber diese unbegrenzte Rekursion ist stark eingeschränkt, ohne undefiniertes Verhalten aufzurufen, wie es der C-Standard dokumentiert 5.2.4.1 Übersetzungsgrenzen Die Implementierung muss in der Lage sein, mindestens ein Programm zu übersetzen und auszuführen, das mindestens eine Instanz jeder der folgenden Grenzen enthält: […] – 15 Verschachtelungsebenen für #included Dateien.

    – chqrlie

    14. Februar um 7:41 Uhr

  • Diese Technik ist gelegentlich als ‘X-Makros’ bezeichnetobwohl ich persönlich den Namen schrecklich finde.

    – Benutzer3840170

    14. Februar um 10:30 Uhr

Benutzeravatar von FooF
FooF

Die akzeptierte Antwort von @chqrlie erklärt zu 100%, was passiert. Dies ist nur ein ergänzender Kommentar.

Wenn Sie C++ verwenden, könnten wir zwei Vorlagenfunktionen definieren, um alle Implementierungen bereitzustellen vsenc8, vsenc16, vsenc32, vsenc64 und vsdec8, vsdec16, vsdec32, vsdec64. Im Gegensatz dazu ist C jedoch eine sehr einfache Sprache und unterstützt keine Templates. Ein gängiger Trick, um die gleiche Leistung (in hässlicher Verpackung) zu haben, besteht darin, die dumme Makrofunktion der Sprache zu verwenden und den C-Präprozessor die entsprechende Arbeit für uns erledigen zu lassen. Die meisten C-Programmierer mit einiger Erfahrung werden im Laufe ihrer Karriere wiederholt auf diese Art von Konstrukt stoßen und sie verwenden.

Was dieses spezielle Beispiel etwas mühsam verständlich macht, ist, dass die Implementierungsdatei unkonventionell fünfmal geparst wird, um zuerst einige vorbereitende Definitionen und dann die vier Varianten der beiden Funktionen zu haben. Der erste Durchgang (innen #ifndef USIZE Präprozessorblock) werden die benötigten Makros und Nicht-Varianten-Sachen definiert und rekursiv #include sich viermal mit unterschiedlichen USIZE Werte (8, 16, 32, 64) als Vorlagenwerte. Bei rekursiver Einbeziehung wird die entsprechende #else Der Präprozessorblock wird mit dem Ergebnis von zwei Funktionen analysiert, die gemäß dem Wert von generiert werden USIZE Makrokonstante, die für den Pass verwendet wird.

Ein konventionellerer, konzeptionell klarerer und sofort verständlicher Weg wäre, die Vorlagenfunktionen beispielsweise aus anderen Dateien einzubinden vsimple.impl:

#define USIZE 8
/* Generate vsenc8(), vsdec8()... */ 
#include "vsimple.impl"

#undef USIZE
#define USIZE 16
/* Generate vsenc16(), vsdec16()... */ 
#include "vsimple.impl"

#undef USIZE
#define USIZE 32
/* Generate vsenc32(), vsdec32()... */ 
#include "vsimple.impl"

#undef USIZE
#define USIZE 64
/* Generate vsenc64(), vsdec64()... */ 
#include "vsimple.impl"

Die includierende Datei vsimple.c und die enthaltene Datei vsimple.impl könnten dann auch viel klarer organisiert werden, was sie wann definieren. Die meisten C-Programmierer würden das Implementierungsmuster erkennen und sofort wissen, was passiert.

Sich selbst auf diese Weise rekursiv und wiederholt einzubeziehen, erweckt ein Gefühl von Hokuspokus, das Applaus für einen verschleierten C-Wettbewerbsbeitrag hervorrufen würde, nicht jedoch für unternehmenskritischen Produktionscode.

  • Guter Vorschlag. Übrigens, <stdio.h>, <stdlib.h> und "vint.h" sind im falschen Teil der enthalten #ifdef Datei, dank include guards kein Problem, aber trotzdem schlampig.

    – chqrlie

    14. Februar um 11:49 Uhr


  • @chqrlie: Ich habe den Punkt, den Sie bereits in Ihrer Antwort angesprochen haben, im Grunde genommen ausgearbeitet und beurteilt: “Diese Verwendung von vorverarbeitetem Quellcode ist bei separaten Dateien häufiger …” 😉

    – FooF

    14. Februar um 11:53 Uhr

  • Ich gehe davon aus, dass der Zweck dieser Vorgehensweise nicht der Verschleierung dient, sondern die Anzahl der Dateien zu reduzieren, die verwaltet werden müssen, wenn dieser Code in ein anderes Projekt kopiert wird.

    – Mosche Katz

    14. Februar um 12:59 Uhr

  • @MosheKatz Ich sage nicht, dass der ursprüngliche Zweck Verschleierung war. Ich wollte nur vermitteln, dass rekursives #sich-einbeziehen eine nützliche Ergänzung im Trickkorb sein könnte, wenn man einen Eintrag im The International C Programming Contest einpfropft (iocc.org). Das Vermeiden des Hinzufügens einer Datei zu einer Liste von dreißig oder etwas mehr Dateien zählt meiner Meinung nach kaum als würdig für die Kosten der Code-Lesbarkeit.

    – FooF

    15. Februar um 2:24 Uhr

Es ist Rekursion. Rekursion ist hier nützlich, da die C-Vorverarbeitung keine Schleifen hat. Darüber hinaus ist es wünschenswert, einen Trick mit einer Datei auszuführen, anstatt mehrere Dateien zu vermehren.

Angenommen, Sie müssten eine Funktion schreiben, die die Ganzzahlen von 1 bis 5 in eine Vorlagenzeichenfolge interpoliert und diese auf der Standardausgabe ausgibt. Angenommen, Sie müssten genau eine Funktion schreiben und durften keine Schleifen verwenden oder kopieren und einfügen printf Aussagen. Sie könnten dies tun:

void template_print(const char *fmt, int n)
{
   if (n == 0) {
     template_print(fmt, 1);
     template_print(fmt, 2);
     template_print(fmt, 3);
     template_print(fmt, 4);
     template_print(fmt, 5);
   } else {
     /* imagine there are 30 lines of statements here we don't want
        to repeat five times. */
     printf(fmt, n);
   }
}

Der oberste Aufruf dazu lautet dann template_print("whatever %d\n", 0) unterscheidet sich durch das Nullargument der n Parameter.

Der Aufruf der obersten Ebene mit 0 ist wie die anfängliche Verarbeitung von vsimple.c ohne USIZE definiert werden.

Die Anforderung an eine Funktion ist analog zu der Anforderung, eine einzelne, in sich geschlossene Funktion zu produzieren .c Datei und nicht eine “Schnittstellen”-Datei, die #includeist eine Implementierung.

1410990cookie-checkWarum enthält diese .c-Datei sich selbst?

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

Privacy policy