C Zeiger auf Array-Deklaration mit Bitweise und Operator

9

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:

  1. iscntrl ('a') wird aufgerufen und 'a' wird in seinen ganzzahligen Wert konvertiert
  2. Überprüfen Sie zuerst, ob _c -1 ist, und geben Sie dann 0 zurück.
  3. Erhöhen Sie die Adresse, auf die der undefinierte Zeiger zeigt, um 1
  4. Deklarieren Sie diese Adresse als Zeiger auf ein Array mit einer Länge (vorzeichenloses Zeichen) ((int) 'a').
  5. 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.

AkzentWool
quelle
1
_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 wie 0x20wird 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 es EOFauch ohne den zusätzlichen Test funktionieren zu lassen .)
Steve Summit
Die Besetzung (unsigned char)besteht darin, sich um die Möglichkeit zu kümmern, dass Zeichen signiert und negativ sind.
Steve Summit

Antworten:

3

_ctype_Es scheint sich um eine eingeschränkte interne Version der Symboltabelle zu handeln, und ich vermute, + 1dass sie sich nicht die Mühe gemacht haben, den Index 0davon 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:

In allen Fällen ist das Argument ein int, dessen Wert als unsigned charoder darstellbar sein soll oder dem Wert des Makros entsprichtEOF

Schritt für Schritt durch den Code gehen:

  • int iscntrl(int _c)Die intTypen sind wirklich Zeichen, aber alle ctype.h-Funktionen müssen verarbeitet werden EOF, also müssen sie es sein int.
  • Die Prüfung gegen -1ist eine Prüfung gegen EOF, 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 ist unsigned char. Beachten Sie, dass chardies 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.
  • Die &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.
  • Alles, was Bit 5 gesetzt hat, gibt den mit 0x20 maskierten Wert zurück, der ein Wert ungleich Null ist. Dies erfüllt die Anforderung, dass die Funktion im Fall von boolean true ungleich Null zurückgibt.
Lundin
quelle
Es ist nicht korrekt, dass die Besetzung die Standardanforderung erfüllt, dass der Wert als darstellbar sein soll unsigned char. Der Standard verlangt, dass der Wert * bereits beim Aufruf der Routine als unsigned charoder gleich darstellbar EOFist. Die Besetzung dient nur als "defensive" Programmierung: Korrigieren des Fehlers eines Programmierers, der ein vorzeichenbehaftetes char(oder ein signed char) Zeichen übergibt, wenn er verpflichtet war, unsigned charbei Verwendung eines ctype.hMakros einen Wert zu übergeben . Es sollte beachtet werden, dass dies den Fehler nicht korrigieren kann, wenn charin einer Implementierung, für die -1 verwendet wird, ein Wert von -1 übergeben wird EOF.
Eric Postpischil
Dies bietet auch eine Erklärung der + 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, EOFfunktionierte der Wert -1 nicht mit diesem Zauber, daher fügte er den bedingten Operator hinzu, um ihn speziell zu behandeln.
Eric Postpischil
3

_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 Zeichens c. Dies ist dasselbe wie zu sagen, dass _ctype_ + 1auf ein Array von 256 Zeichen (_ctype_ + 1)[c]verwiesen wird, wobei die Kategorie des Zeichens dargestellt wird c.

(_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)_cdes Arrays zu, die bei beginnt (_ctype_ + 1).

Der Code, der _cvon intbis umgewandelt unsigned charwird , ist nicht unbedingt erforderlich: ctype-Funktionen übernehmen Zeichenwerte, die umgewandelt wurden unsigned char( charist in OpenBSD signiert): Ein korrekter Aufruf ist char c; … iscntrl((unsigned char)c). Sie haben den Vorteil, dass kein Pufferüberlauf auftritt: Wenn die Anwendung iscntrlmit einem Wert aufruft , der außerhalb des Bereichs von unsigned 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 sie cnicht -1 ist.

Der Grund für den Sonderfall mit -1 ist, dass es ist EOF. Viele Standard-C-Funktionen, die charbeispielsweise mit a arbeiten getchar, stellen das Zeichen als intWert dar, bei dem es sich um den Zeichenwert handelt, der in einen positiven Bereich eingeschlossen ist, und verwenden den speziellen Wert, EOF == -1um anzuzeigen, dass kein Zeichen gelesen werden konnte. Für Funktionen wie getchar, EOFzeigt das Ende der Datei, daher der Name e nd- o f- f ile. Eric Postpischil schlägt vor, dass der Code ursprünglich gerecht war return _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 vder im Array gefundene Wert ist, wird geprüft, v & _Cob das Bit at gesetzt 0x20ist v. Die Werte im Array sind Masken der Kategorien, in denen sich das Zeichen befindet: _Cwird für Steuerzeichen festgelegt, _Uwird für Großbuchstaben festgelegt usw.

Gilles 'SO - hör auf böse zu sein'
quelle
(_ctype_ + 1)[_c] würde den korrekten Array-Index verwenden, wie vom C-Standard angegeben, da es in der Verantwortung des Benutzers liegt, entweder EOFoder einen unsigned charWert 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 wird EOF.
Eric Postpischil
Dies bietet auch eine Erklärung der + 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, EOFfunktionierte der Wert -1 nicht mit diesem Zauber, daher fügte er den bedingten Operator hinzu, um ihn speziell zu behandeln.
Eric Postpischil
2

Ich beginne mit Schritt 3:

Erhöhen Sie die Adresse, auf die der undefinierte Zeiger zeigt, um 1

Der Zeiger ist nicht undefiniert. Es ist nur in einer anderen Kompilierungseinheit definiert. Das externsagt 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.

4386427
quelle
1

Alle Informationen hier basieren auf der Analyse des Quellcodes (und der Programmiererfahrung).

Die Erklärung

extern const char *_ctype_;

teilt dem Compiler mit, dass es einen Zeiger auf eine const charbenannte Stelle gibt _ctype_.

(4) Auf diesen Zeiger wird als Array zugegriffen.

(_ctype_ + 1)[(unsigned char)_c]

Die Umwandlung (unsigned char)_cstellt sicher, dass der Indexwert im Bereich von unsigned char(0..255) liegt.

Die Zeigerarithmetik _ctype_ + 1verschiebt 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 die 0Zeichenwerte verwenden, 255bleibt 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.

Bodo
quelle
Ich denke, sie haben + 1den Zeiger verschoben , um zu verdeutlichen, dass sie 1..256stattdessen auf Elemente zugreifen 1..255,0. _ctype_[1 + (unsigned char)_c]wäre aufgrund der impliziten Umstellung auf gleichwertig gewesen int. Und _ctype_[(_c & 0xff) + 1]wäre noch klarer und prägnanter gewesen.
cmaster
0

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 + 1des 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:

#include <stdio.h>
int main() {
    // Code like the following two lines will be defined somewhere in the run-time
    // libraries with which your program is linked, only using _ctype_ in place of _qlist_ ...
    const char list[] = "abcdefghijklmnopqrstuvwxyz";
    const char* _qlist_ = list;
    // These two lines show how expressions like (a)[b] and (a+1)[b] just boil down to
    // a[b] and a[b+1], respectively ...
    char p = (_qlist_)[6];
    char q = (_qlist_ + 1)[6];
    printf("p = %c  q = %c\n", p, q);
    return 0;
}

Fühlen Sie sich frei, um weitere Klarstellung und / oder Erklärung zu bitten.

Adrian Mole
quelle
0

Die in ctype.hakzeptierten Funktionen akzeptieren Objekte des Typs int. Für Zeichen, die als Argumente verwendet werden, wird angenommen, dass sie vorläufig in den Typ umgewandelt wurden unsigned char. Dieses Zeichen wird als Index in einer Tabelle verwendet, die die Charakteristik des Zeichens bestimmt.

Es scheint, dass die Prüfung _c == -1für den Fall verwendet wird, dass die _cden Wert von enthält EOF. Ist dies nicht der EOFFall, 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 Bit 0x20gesetzt ist, ist das Zeichen ein Steuersymbol.

Den Ausdruck verstehen

(_ctype_ + 1)[(unsigned char)_c]

Berücksichtigen Sie, dass das Array-Subskription ein Postfix-Operator ist, der wie folgt definiert ist

postfix-expression [ expression ]

Du darfst nicht gerne schreiben

_ctype_ + 1[(unsigned char)_c]

weil dieser Ausdruck äquivalent zu ist

_ctype_ + ( 1[(unsigned char)_c] )

Der Ausdruck _ctype_ + 1ist also in Klammern eingeschlossen, um einen primären Ausdruck zu erhalten.

In der Tat haben Sie

pointer[integral_expression]

Dies ergibt das Objekt eines Arrays am Index, das als Ausdruck berechnet wird, in dem sich der integral_expressionZeiger befindet (_ctype_ + 1)(gere wird als Zeiger arithmetuc verwendet), und integral_expressiondas heißt, der Index ist der Ausdruck (unsigned char)_c.

Vlad aus Moskau
quelle