Unterstriche in C++: Stille Killer Ihres Codes
\n\nStellen Sie sich vor: Sie schreiben sauberen, schönen C++-Code. Der Compiler schweigt, der Linter meckert nicht, alles funktioniert perfekt. Und dann – peng! – und auf einem anderen Compiler oder mit einer neuen Standardversion bricht alles ohne ersichtlichen Grund zusammen. Kommt Ihnen bekannt vor?
\n\nMeistens liegt die Ursache des Übels in scheinbar harmlosen Unterstrichzeichen (_). Dieses Thema ist ein wahres "Friedhof" für Anfänger und sogar erfahrene C++-Entwickler. Nicht umsonst hat die Frage auf Stack Overflow zu den Regeln der Verwendung von Unterstrichen Zehntausende von Aufrufen erhalten. Lassen wir uns ein für alle Mal klarmachen, was geht, was nicht geht und wie man nicht in die Falle des undefinierten Verhaltens (UB) tappt.
Warum ist der C++-Standard so streng mit Unterstrichen?
\n\nDer Kern des Problems liegt in der Kompatibilität und dem globalen Namensraum. Die C++-Standardbibliothek verwendet aktiv Unterstriche für ihre internen Zwecke. Wenn Sie versehentlich einen Bezeichner erstellen, der mit internen Namen der Bibliothek oder des Compilers kollidiert – dann ist alles verloren. Der Compiler kann Ihre Variable stillschweigend neu definieren, und Sie erhalten undefiniertes Verhalten (Undefined Behavior), das sich im ungünstigsten Moment zeigt.
\n\nDer C++-Standard (ab C++03 und in allen folgenden Versionen, einschließlich C++20 und C++23) legt klar fest, welche Bezeichner für die Implementierung reserviert sind. Ein Verstoß gegen diese Regeln macht Ihren Code nicht nur inkorrekt, sondern nicht portierbar.
\n\nDie Top 5 tödlichen Fehler mit Unterstrichen
\n\nLassen Sie uns die häufigsten Fallstricke analysieren, über die selbst erfahrene Programmierer stolpern.
\n\nFehler Nr. 1: Doppelter Unterstrich an beliebiger Stelle
\n\nRegel: Jeder Bezeichner, der zwei aufeinanderfolgende Unterstrichzeichen (__) an beliebiger Stelle enthält, ist für die Implementierung reserviert.
// SCHLECHT! Für die "Implementierung" reserviert\nint __myVariable = 42;\nvoid my__function();\nclass My__Class {};\n\n// GUT\nint myVariable = 42;\nvoid my_function();\nclass MyClass {};\n\n\nWarum ist das gefährlich? Der Compiler könnte __myVariable für seine internen Makros verwenden. Beispielsweise definieren viele Compiler __LINE__ oder __cplusplus. Wenn Sie Ihr eigenes __LINE__ erstellen, könnte der Compiler Ihre Definition einfach ignorieren oder, schlimmer noch, durch seinen eigenen Wert ersetzen. Das Ergebnis ist UB.
Fehler Nr. 2: Unterstrich + Großbuchstabe im globalen Gültigkeitsbereich
\n\nRegel: Ein Bezeichner, der mit einem Unterstrich beginnt, gefolgt von einem Großbuchstaben (A-Z), ist für die Implementierung in jedem Gültigkeitsbereich reserviert.
\n\n// SCHLECHT! Sogar im lokalen Gültigkeitsbereich!\nvoid foo() {\n int _MyVar = 10; // Regelverstoß\n}\n\n// SCHLECHT! Global erst recht\nint _GlobalVar = 20;\n\n// GUT\nvoid foo() {\n int myVar = 10;\n}\n\n\nDiese Regel ist die tückischste. Viele denken: "Nun, ich bin doch innerhalb einer Funktion, wen stört das schon?". Es stört! Der Standard macht keine Ausnahmen für den lokalen Gültigkeitsbereich, wenn der Bezeichner mit einem Unterstrich und einem Großbuchstaben beginnt. Der Compiler hat das volle Recht, solche Namen für seine Zwecke zu reservieren.
\n\nFehler Nr. 3: Unterstrich am Anfang + Kleinbuchstabe im globalen Gültigkeitsbereich
\n\nRegel: Ein Bezeichner, der mit einem Unterstrich beginnt, gefolgt von einem Kleinbuchstaben (a-z), ist für die Implementierung im globalen Gültigkeitsbereich (Namespace-Bereich) reserviert.
\n\n// SCHLECHT! Im globalen Namensraum\nint _myGlobal = 0;\nvoid _helperFunction();\n\n// GUT – innerhalb einer Funktion oder Klasse\nclass MyClass {\n int _memberVar; // Innerhalb der Klasse – okay (aber besser m_)\n};\n\nvoid foo() {\n int _localVar = 5; // Innerhalb der Funktion – okay (aber besser localVar)\n}\n