1110 Stimmen

Warum interpretiert der C-Präprozessor das Wort "linux" als die Konstante "1"?

Warum interpretiert der C-Präprozessor in GCC das Wort linux (kleine Buchstaben) als Konstante 1?

test.c:

#include 
int main(void)
{       
    int linux = 5;
    return 0;
}

Ergebnis von $ gcc -E test.c (nach der Vorverarbeitungsphase stoppen):

....
int main(void)
{
    int 1 = 5;
    return 0;
}

Was natürlich zu einem Fehler führt.

(Übrigens: Es gibt kein #define linux in der Datei stdio.h.)

1022voto

Keith Thompson Punkte 240701

In den alten Tagen (vor ANSI) war das Vordefinieren von Symbolen wie unix und vax eine Möglichkeit, dem Code zu ermöglichen, zur Kompilierzeit zu erkennen, für welches System er kompiliert wurde. Damals gab es keinen offiziellen Sprachstandard (außer den Referenzmaterialien am Ende der ersten Ausgabe von K&R, 1978) und C-Code jeder Komplexität war typischerweise ein komplexes Labyrinth von #ifdefs, um Unterschiede zwischen Systemen zu ermöglichen. Diese Makrodefinitionen wurden im Allgemeinen vom Compiler selbst festgelegt, nicht in einer Bibliothekskopfdatei definiert. Da es damals keine echten Regeln dafür gab, welche Bezeichner von der Implementierung verwendet werden konnten und welche für Programmierer reserviert waren, fühlten sich Compiler-Entwickler frei, einfache Namen wie unix zu verwenden und gingen davon aus, dass Programmierer einfach solche Namen für ihre eigenen Zwecke nicht verwenden würden.

Der ANSI-C-Standard von 1989 führte Regeln ein, die beschränkten, welche Symbole eine Implementierung legal vordefinieren konnte. Ein vom Compiler vordefiniertes Makro konnte nur einen Namen haben, der mit zwei Unterstrichen beginnt oder mit einem Unterstrich gefolgt von einem Großbuchstaben, und ließ Programmierer frei, Bezeichner zu verwenden, die nicht diesem Muster entsprechen und nicht in der Standardbibliothek verwendet werden.

Als Folge davon ist jeder Compiler, der unix oder linux vordefiniert, nicht konform, da er nicht in der Lage sein wird, völlig legalen Code zu kompilieren, der etwas wie int linux = 5; verwendet.

Wie es scheint, ist gcc standardmäßig nicht konform - aber es kann durch die richtigen Befehlszeilenoptionen (vernünftig gut) konform gemacht werden:

gcc -std=c90 -pedantic ... # oder -std=c89 oder -ansi
gcc -std=c99 -pedantic
gcc -std=c11 -pedantic

Wenn du das hier liest, wird gcc wahrscheinlich neuere Ausgaben des C-Standards unterstützen. Siehe das gcc-Handbuch für weitere Details.

gcc könnte diese Definitionen (unix, linux, usw.) in zukünftigen Versionen auslaufen lassen, daher solltest du keinen Code schreiben, der von ihnen abhängt. Wenn dein Programm wissen muss, ob es für ein Linux-Ziel kompiliert wird oder nicht, kann es überprüfen, ob __linux__ definiert ist (vorausgesetzt, du verwendest gcc oder einen kompatiblen Compiler). Siehe das GNU C Vorkompilierer-Handbuch für weitere Informationen.

Eine ziemlich irrelevante Randnotiz: Der "Best One Liner"-Gewinner des International Obfuscated C Code Contest von 1987, von David Korn (ja, dem Autor der Korn-Shell), nutzte das vordefinierte unix-Makro:

main() { printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60);}

Es druckt "unix", aber aus Gründen, die absolut nichts mit der Schreibweise des Makronamens zu tun haben.

Ich möchte hier keine Spoiler posten und ermutige jeden, der dies liest, zuerst zu versuchen, diesen Code selbst zu verstehen. Aber wenn du wirklich aufgeben willst, habe ich hier eine Erklärung gepostet: https://gist.github.com/Keith-S-Thompson/6920347

196voto

rici Punkte 218780

Dies scheint eine (undokumentierte) "GNU-Erweiterung" zu sein: [Korrektur: Ich habe schließlich eine Erwähnung in den Dokumenten gefunden. Siehe unten.]

Der folgende Befehl verwendet die Option -dM, um alle Preprozessor-Definitionen auszudrucken. Da die Eingabedatei "leer" ist, zeigt sie genau die vordefinierten Makros an. Es wurde mit gcc-4.7.3 auf einer Standard-Ubuntu-Installation ausgeführt. Man kann sehen, dass der Preprozessor den Standard berücksichtigt. Insgesamt gibt es 243 Makros mit -std=gnu99 und 240 mit -std=c99; Ich habe die Ausgabe auf Relevanz gefiltert.

$ cpp --std=c89 -dM < /dev/null | grep linux
#define __linux 1
#define __linux__ 1
#define __gnu_linux__ 1

$ cpp --std=gnu89 -dM < /dev/null | grep linux
#define __linux 1
#define __linux__ 1
#define __gnu_linux__ 1
#define linux 1

$ cpp --std=c99 -dM < /dev/null | grep linux
#define __linux 1
#define __linux__ 1
#define __gnu_linux__ 1

$ cpp --std=gnu99 -dM < /dev/null | grep linux
#define __linux 1
#define __linux__ 1
#define __gnu_linux__ 1
#define linux 1

Die "GNU-Standard"-Versionen definieren auch #define unix. (Die Verwendung von c11 und gnu11 ergibt dieselben Ergebnisse.)

Ich nehme an, sie hatten ihre Gründe, aber es scheint mir, dass die Standardinstallation von gcc (die C-Code mit -std=gnu89 kompiliert, es sei denn, es wird anders angegeben) nicht konform ist und - wie in dieser Frage - überraschend. Die globale Namespace mit Makros zu verunreinigen, deren Namen nicht mit einem Unterstrich beginnen, ist in einer konformen Implementierung nicht zulässig. (6.8.10p2: "Alle anderen vordefinierten Makronamen müssen mit einem führenden Unterstrich gefolgt von einem Großbuchstaben oder einem zweiten Unterstrich beginnen", aber wie in Anhang J.5 (Portabilitätsprobleme) erwähnt, sind solche Namen oft vordefiniert.)

Als ich diese Antwort ursprünglich schrieb, konnte ich keine Dokumentation in gcc zu diesem Thema finden, aber ich habe es schließlich entdeckt, nicht in der C-implementation-defined behavior noch in den C-Erweiterungen, sondern im cpp-Handbuchabschnitt 3.7.3, wo es heißt:

Wir fahren langsam alle vordefinierten Makros außerhalb des reservierten Namensraums aus. Sie sollten sie niemals in neuen Programmen verwenden...

45voto

rodrigo Punkte 87935

Von info gcc (Hervorhebung von mir):

-ansi

Im C-Modus entspricht dies -std=c90. Im C++-Modus ist es äquivalent zu -std=c++98. Dadurch werden bestimmte Funktionen von GCC deaktiviert, die mit ISO C90 (beim Kompilieren von C-Code) oder mit Standard-C++ (beim Kompilieren von C++-Code) inkompatibel sind, wie die Schlüsselwörter asm und typeof, und vordefinierte Makros wie 'unix' und 'vax', die den Typ des Systems identifizieren, das Sie verwenden. Es aktiviert auch das unerwünschte und selten verwendete ISO- Trigraphen-Feature. Für den C-Compiler deaktiviert es die Erkennung von C++-Stil // Kommentaren sowie das Schlüsselwort inline.

(Es verwendet vax im Beispiel anstelle von linux, weil es zum Zeitpunkt des Verfassens vielleicht beliebter war ;-).

Die Grundidee ist, dass GCC nur versucht, vollständig den ISO-Standards zu entsprechen, wenn es mit der Option -ansi aufgerufen wird.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X