Ich möchte den folgenden Code verstehen:
//...
#define _C 0x20
extern const char *_ctype_;
//...
__only_inline int iscntrl(int _c)
{
return (_c == -1 ? 0 : ((_ctype_ + 1)[(unsigned char)_c] & _C));
}
Es stammt aus der Datei ctype.h aus dem Quellcode des Betriebssystems obenbsd. Diese Funktion prüft, ob ein Zeichen ein Steuerzeichen oder ein druckbarer Buchstabe innerhalb des ASCII-Bereichs ist. Dies ist meine aktuelle Gedankenkette:
- iscntrl ('a') wird aufgerufen und 'a' wird in seinen ganzzahligen Wert konvertiert
- Überprüfen Sie zuerst, ob _c -1 ist, und geben Sie dann 0 zurück.
- Erhöhen Sie die Adresse, auf die der undefinierte Zeiger zeigt, um 1
- Deklarieren Sie diese Adresse als Zeiger auf ein Array mit einer Länge (vorzeichenloses Zeichen) ((int) 'a').
- Wende das Bit und den Operator auf _C (0x20) und das Array (???) an.
Seltsamerweise funktioniert es irgendwie und jedes Mal, wenn 0 zurückgegeben wird, ist das angegebene Zeichen _c kein druckbares Zeichen. Andernfalls gibt die Funktion beim Drucken nur einen ganzzahligen Wert zurück, der nicht von besonderem Interesse ist. Mein Verständnisproblem ist in Schritt 3, 4 (ein bisschen) und 5.
Vielen Dank für jede Hilfe.
_ctype_
ist im Wesentlichen eine Reihe von Bitmasken. Es wird nach dem Charakter des Interesses indiziert. So_ctype_['A']
Bits enthalten würde „Alpha“ entspricht , und „Großbuchstaben“,_ctype_['a']
würde Bits entsprechend „Alpha“ und „kleinen“ enthalten,_ctype_['1']
würde ein wenig enthalten , um „digit“ entspricht, usw. Es sieht aus wie0x20
wird das Bit entsprechend „Kontrolle“ . Aber aus irgendeinem Grund ist das_ctype_
Array um 1 versetzt, so dass die Bits für'a'
wirklich in sind_ctype_['a'+1]
. (Das war wahrscheinlich, um esEOF
auch ohne den zusätzlichen Test funktionieren zu lassen .)(unsigned char)
besteht darin, sich um die Möglichkeit zu kümmern, dass Zeichen signiert und negativ sind.Antworten:
_ctype_
Es scheint sich um eine eingeschränkte interne Version der Symboltabelle zu handeln, und ich vermute,+ 1
dass sie sich nicht die Mühe gemacht haben, den Index0
davon zu speichern , da dieser nicht druckbar ist. Oder sie verwenden möglicherweise eine 1-indizierte Tabelle anstelle einer 0-indizierten Tabelle, wie es in C üblich ist.Der C-Standard schreibt dies für alle ctype.h-Funktionen vor:
Schritt für Schritt durch den Code gehen:
int iscntrl(int _c)
Dieint
Typen sind wirklich Zeichen, aber alle ctype.h-Funktionen müssen verarbeitet werdenEOF
, also müssen sie es seinint
.-1
ist eine Prüfung gegenEOF
, da sie den Wert hat-1
._ctype+1
ist eine Zeigerarithmetik, um eine Adresse eines Array-Elements abzurufen.[(unsigned char)_c]
ist einfach ein Array-Zugriff auf dieses Array, bei dem die Umwandlung vorhanden ist, um die Standardanforderung zu erzwingen, dass der Parameter als darstellbar istunsigned char
. Beachten Sie, dasschar
dies tatsächlich einen negativen Wert enthalten kann. Dies ist also eine defensive Programmierung. Das Ergebnis des[]
Array-Zugriffs ist ein einzelnes Zeichen aus der internen Symboltabelle.&
Maskierung dient dazu, eine bestimmte Gruppe von Zeichen aus der Symboltabelle abzurufen. Anscheinend sind alle Zeichen mit gesetztem Bit 5 (Maske 0x20) Steuerzeichen. Es macht keinen Sinn, dies zu sehen, ohne die Tabelle anzusehen.quelle
unsigned char
. Der Standard verlangt, dass der Wert * bereits beim Aufruf der Routine alsunsigned char
oder gleich darstellbarEOF
ist. Die Besetzung dient nur als "defensive" Programmierung: Korrigieren des Fehlers eines Programmierers, der ein vorzeichenbehafteteschar
(oder einsigned char
) Zeichen übergibt, wenn er verpflichtet war,unsigned char
bei Verwendung einesctype.h
Makros einen Wert zu übergeben . Es sollte beachtet werden, dass dies den Fehler nicht korrigieren kann, wennchar
in einer Implementierung, für die -1 verwendet wird, ein Wert von -1 übergeben wirdEOF
.+ 1
. Wenn das Makro diese defensive Anpassung zuvor nicht enthalten hätte, hätte es lediglich so implementiert werden können((_ctype_+1)[_c] & _C)
, dass eine Tabelle mit den Voranpassungswerten -1 bis 255 indiziert worden wäre. Der erste Eintrag wurde also nicht übersprungen und diente einem Zweck. Wenn jemand später den defensiven Zauber hinzufügte,EOF
funktionierte der Wert -1 nicht mit diesem Zauber, daher fügte er den bedingten Operator hinzu, um ihn speziell zu behandeln._ctype_
ist ein Zeiger auf ein globales Array von 257 Bytes. Ich weiß nicht, wofür_ctype_[0]
verwendet wird._ctype_[1]
bis_ctype_[256]_
stellen die Zeichenkategorien der Zeichen 0,… bzw. 255 dar:_ctype_[c + 1]
repräsentiert die Kategorie des Zeichensc
. Dies ist dasselbe wie zu sagen, dass_ctype_ + 1
auf ein Array von 256 Zeichen(_ctype_ + 1)[c]
verwiesen wird, wobei die Kategorie des Zeichens dargestellt wirdc
.(_ctype_ + 1)[(unsigned char)_c]
ist keine Erklärung. Es ist ein Ausdruck, der den Array-Indexoperator verwendet. Es greift auf die Position(unsigned char)_c
des Arrays zu, die bei beginnt(_ctype_ + 1)
.Der Code, der
_c
vonint
bis umgewandeltunsigned char
wird , ist nicht unbedingt erforderlich: ctype-Funktionen übernehmen Zeichenwerte, die umgewandelt wurdenunsigned char
(char
ist in OpenBSD signiert): Ein korrekter Aufruf istchar c; … iscntrl((unsigned char)c)
. Sie haben den Vorteil, dass kein Pufferüberlauf auftritt: Wenn die Anwendungiscntrl
mit einem Wert aufruft , der außerhalb des Bereichs vonunsigned char
-1 liegt und nicht -1 ist, gibt diese Funktion einen Wert zurück, der möglicherweise nicht aussagekräftig ist, aber zumindest keinen verursacht Ein Absturz oder ein Leck privater Daten, die sich zufällig an der Adresse außerhalb der Array-Grenzen befanden. Der Wert ist sogar dann korrekt, wenn die Funktion aufgerufen wird,char c; … iscntrl(c)
solange siec
nicht -1 ist.Der Grund für den Sonderfall mit -1 ist, dass es ist
EOF
. Viele Standard-C-Funktionen, diechar
beispielsweise mit a arbeitengetchar
, stellen das Zeichen alsint
Wert dar, bei dem es sich um den Zeichenwert handelt, der in einen positiven Bereich eingeschlossen ist, und verwenden den speziellen Wert,EOF == -1
um anzuzeigen, dass kein Zeichen gelesen werden konnte. Für Funktionen wiegetchar
,EOF
zeigt das Ende der Datei, daher der Name e nd- o f- f ile. Eric Postpischil schlägt vor, dass der Code ursprünglich gerecht warreturn _ctype_[_c + 1]
, und das ist wahrscheinlich richtig:_ctype_[0]
wäre der Wert für EOF. Diese einfachere Implementierung führt zu einem Pufferüberlauf, wenn die Funktion missbraucht wird, während die aktuelle Implementierung dies vermeidet, wie oben erläutert.Wenn
v
der im Array gefundene Wert ist, wird geprüft,v & _C
ob das Bit at gesetzt0x20
istv
. Die Werte im Array sind Masken der Kategorien, in denen sich das Zeichen befindet:_C
wird für Steuerzeichen festgelegt,_U
wird für Großbuchstaben festgelegt usw.quelle
(_ctype_ + 1)[_c]
würde den korrekten Array-Index verwenden, wie vom C-Standard angegeben, da es in der Verantwortung des Benutzers liegt, entwederEOF
oder einenunsigned char
Wert zu übergeben. Das Verhalten für andere Werte ist nicht durch den C-Standard definiert. Die Besetzung dient nicht zur Implementierung des vom C-Standard geforderten Verhaltens. Dies ist eine Problemumgehung, um Fehler zu vermeiden, die von Programmierern verursacht werden, die negative Zeichenwerte falsch übergeben. Es ist jedoch unvollständig oder falsch (und kann nicht korrigiert werden), da ein Wert von -1 Zeichen notwendigerweise als behandelt wirdEOF
.+ 1
. Wenn das Makro diese defensive Anpassung zuvor nicht enthalten hätte, hätte es lediglich so implementiert werden können((_ctype_+1)[_c] & _C)
, dass eine Tabelle mit den Voranpassungswerten -1 bis 255 indiziert worden wäre. Der erste Eintrag wurde also nicht übersprungen und diente einem Zweck. Wenn jemand später den defensiven Zauber hinzufügte,EOF
funktionierte der Wert -1 nicht mit diesem Zauber, daher fügte er den bedingten Operator hinzu, um ihn speziell zu behandeln.Ich beginne mit Schritt 3:
Der Zeiger ist nicht undefiniert. Es ist nur in einer anderen Kompilierungseinheit definiert. Das
extern
sagt der Teil dem Compiler. Wenn also alle Dateien miteinander verknüpft sind, löst der Linker die Verweise darauf auf.Worauf weist es also hin?
Es zeigt auf ein Array mit Informationen zu jedem Zeichen. Jedes Zeichen hat einen eigenen Eintrag. Ein Eintrag ist eine Bitmap-Darstellung von Merkmalen für das Zeichen. Beispiel: Wenn Bit 5 gesetzt ist, bedeutet dies, dass das Zeichen ein Steuerzeichen ist. Ein weiteres Beispiel: Wenn Bit 0 gesetzt ist, bedeutet dies, dass das Zeichen ein oberes Zeichen ist.
So etwas wie
(_ctype_ + 1)['x']
wird die Eigenschaften erhalten, die für zutreffen'x'
. Dann wird bitweise und ausgeführt, um zu prüfen, ob Bit 5 gesetzt ist, dh ob es sich um ein Steuerzeichen handelt.Der Grund für das Hinzufügen von 1 ist wahrscheinlich, dass der reale Index 0 für einen bestimmten Zweck reserviert ist.
quelle
Alle Informationen hier basieren auf der Analyse des Quellcodes (und der Programmiererfahrung).
Die Erklärung
teilt dem Compiler mit, dass es einen Zeiger auf eine
const char
benannte Stelle gibt_ctype_
.(4) Auf diesen Zeiger wird als Array zugegriffen.
Die Umwandlung
(unsigned char)_c
stellt sicher, dass der Indexwert im Bereich vonunsigned char
(0..255) liegt.Die Zeigerarithmetik
_ctype_ + 1
verschiebt die Array-Position effektiv um 1 Element. Ich weiß nicht, warum sie das Array so implementiert haben. Wenn Sie den Bereich_ctype_[1]
.._ctype[256]
für die0
Zeichenwerte verwenden,255
bleibt der Wert_ctype_[0]
für diese Funktion unbenutzt. (Der Offset von 1 kann auf verschiedene alternative Arten implementiert werden.)Der Array-Zugriff ruft einen Wert (vom Typ
char
, um Platz zu sparen) ab, wobei der Zeichenwert als Array-Index verwendet wird.(5) Die bitweise UND-Verknüpfung extrahiert ein einzelnes Bit aus dem Wert.
Anscheinend wird der Wert aus dem Array als Bitfeld verwendet, in dem das Bit 5 (von 0 beginnend mit mindestens einem signifikanten Bit, =
0x20
) ein Flag für "ist ein Steuerzeichen" ist. Das Array enthält also Bitfeldwerte, die die Eigenschaften der Zeichen beschreiben.quelle
+ 1
den Zeiger verschoben , um zu verdeutlichen, dass sie1..256
stattdessen auf Elemente zugreifen1..255,0
._ctype_[1 + (unsigned char)_c]
wäre aufgrund der impliziten Umstellung auf gleichwertig gewesenint
. Und_ctype_[(_c & 0xff) + 1]
wäre noch klarer und prägnanter gewesen.Der Schlüssel hier ist zu verstehen, was der Ausdruck
(_ctype_ + 1)[(unsigned char)_c]
tut (der dann der bitweisen und Operation zugeführt wird& 0x20
, um das Ergebnis zu erhalten!Kurze Antwort: Es wird ein Element
_c + 1
des Arrays zurückgegeben, auf das gezeigt wird_ctype_
.Wie?
Erstens, obwohl Sie denken, dass
_ctype_
es undefiniert ist , ist es tatsächlich nicht! Der Header deklariert ihn als externe Variable - er wird jedoch (mit ziemlicher Sicherheit) in einer der Laufzeitbibliotheken definiert, mit denen Ihr Programm beim Erstellen verknüpft ist.Versuchen Sie, das folgende kurze Programm durchzuarbeiten (sogar zu kompilieren), um zu veranschaulichen, wie die Syntax der Array-Indizierung entspricht:
Fühlen Sie sich frei, um weitere Klarstellung und / oder Erklärung zu bitten.
quelle
Die in
ctype.h
akzeptierten Funktionen akzeptieren Objekte des Typsint
. Für Zeichen, die als Argumente verwendet werden, wird angenommen, dass sie vorläufig in den Typ umgewandelt wurdenunsigned char
. Dieses Zeichen wird als Index in einer Tabelle verwendet, die die Charakteristik des Zeichens bestimmt.Es scheint, dass die Prüfung
_c == -1
für den Fall verwendet wird, dass die_c
den Wert von enthältEOF
. Ist dies nicht derEOF
Fall, wird _c in den Typ unsigned char umgewandelt, der als Index in der Tabelle verwendet wird, auf die der Ausdruck zeigt_ctype_ + 1
. Und wenn das von der Maske angegebene Bit0x20
gesetzt ist, ist das Zeichen ein Steuersymbol.Den Ausdruck verstehen
Berücksichtigen Sie, dass das Array-Subskription ein Postfix-Operator ist, der wie folgt definiert ist
Du darfst nicht gerne schreiben
weil dieser Ausdruck äquivalent zu ist
Der Ausdruck
_ctype_ + 1
ist also in Klammern eingeschlossen, um einen primären Ausdruck zu erhalten.In der Tat haben Sie
Dies ergibt das Objekt eines Arrays am Index, das als Ausdruck berechnet wird, in dem sich der
integral_expression
Zeiger befindet(_ctype_ + 1)
(gere wird als Zeiger arithmetuc verwendet), undintegral_expression
das heißt, der Index ist der Ausdruck(unsigned char)_c
.quelle