Puntatore C alla dichiarazione dell'array con bit a bit e operatore


9

Voglio capire il seguente codice:

//...
#define _C 0x20
extern const char *_ctype_;
//...
__only_inline int iscntrl(int _c)
{
    return (_c == -1 ? 0 : ((_ctype_ + 1)[(unsigned char)_c] & _C));
}

Ha origine dal file ctype.h dal codice sorgente del sistema operativo obenbsd. Questa funzione controlla se un carattere è un carattere di controllo o una lettera stampabile all'interno dell'intervallo ascii. Questa è la mia attuale catena di pensiero:

  1. viene chiamato iscntrl ('a') e 'a' viene convertito nel suo valore intero
  2. controlla prima se _c è -1, quindi restituisce 0 else ...
  3. incrementa l'indirizzo a cui punta il puntatore indefinito di 1
  4. dichiarare questo indirizzo come puntatore a un array di lunghezza (unsigned char) ((int) 'a')
  5. applica bit a bit e operatore a _C (0x20) e all'array (???)

In qualche modo, stranamente, funziona e ogni volta che viene restituito 0 il dato char _c non è un carattere stampabile. Altrimenti quando è stampabile la funzione restituisce solo un valore intero che non ha alcun interesse speciale. Il mio problema di comprensione è nel passaggio 3, 4 (un po ') e 5.

Grazie per tutto l'aiuto.


1
_ctype_è essenzialmente una matrice di maschere di bit. Viene indicizzato dal personaggio di interesse. Conterrebbe quindi i _ctype_['A']bit corrispondenti a "alfa" e "lettere maiuscole", _ctype_['a']conterrebbe i bit corrispondenti a "alfa" e "lettere minuscole", _ctype_['1']conterrebbe un bit corrispondente a "cifre", ecc. Sembra che 0x20sia il bit corrispondente a "controllo" . Ma per qualche ragione l' _ctype_array è sfalsato di 1, quindi i bit per 'a'sono veramente dentro _ctype_['a'+1]. (Probabilmente era per farlo funzionare EOFanche senza il test extra.)
Steve Summit

Il cast (unsigned char)è quello di prendersi cura della possibilità che i personaggi siano firmati e negativi.
Steve Summit,

Risposte:


3

_ctype_sembra essere una versione interna limitata della tabella dei simboli e suppongo + 1sia che non si siano preoccupati di salvarne l'indice 0poiché non è stampabile. O forse stanno usando una tabella 1 indicizzata invece di 0 indicizzata come è personalizzato in C.

Lo standard C lo impone per tutte le funzioni di ctype.h:

In tutti i casi l'argomento è an int, il cui valore deve essere rappresentabile come unsigned charo deve essere uguale al valore della macroEOF

Esaminando il codice passo dopo passo:

  • int iscntrl(int _c)I inttipi sono in realtà caratteri, ma tutte le funzioni di ctype.h sono necessarie per essere gestite EOF, quindi devono essere int.
  • Il controllo contro -1è un controllo contro EOF, poiché ha il valore -1.
  • _ctype+1 è l'aritmetica del puntatore per ottenere un indirizzo di un elemento dell'array.
  • [(unsigned char)_c]è semplicemente un accesso di array di quell'array, dove il cast è lì per far rispettare il requisito standard del parametro che è rappresentabile come unsigned char. Si noti che charpuò effettivamente contenere un valore negativo, quindi questa è una programmazione difensiva. Il risultato dell'accesso alla []matrice è un singolo carattere dalla loro tabella dei simboli interna.
  • Il &mascheramento è lì per ottenere un certo gruppo di personaggi dalla tabella dei simboli. Apparentemente tutti i caratteri con il bit 5 impostato (maschera 0x20) sono caratteri di controllo. Non ha senso farlo senza vedere il tavolo.
  • Qualsiasi cosa con il bit 5 impostato restituirà il valore mascherato con 0x20, che è un valore diverso da zero. Questo soddisfa il requisito della funzione che restituisce un valore diverso da zero in caso di valore booleano true.

Non è corretto che il cast soddisfi i requisiti standard per cui il valore è rappresentabile come unsigned char. Lo standard richiede che il valore * sia già rappresentabile unsigned charo uguale EOFquando viene chiamata la routine. Il cast serve solo come programmazione "difensiva": correggere l'errore di un programmatore che passa un segno char(o un signed char) quando l'onere era su di essi per passare un unsigned charvalore quando si utilizza una ctype.hmacro. Va notato che ciò non può correggere l'errore quando charviene passato un valore di −1 in un'implementazione che usa −1 per EOF.
Eric Postpischil,

Questo offre anche una spiegazione del + 1. Se la macro non contenesse in precedenza questa correzione difensiva, avrebbe potuto essere implementata semplicemente come ((_ctype_+1)[_c] & _C), avendo quindi una tabella indicizzata con i valori di pre-regolazione da -1 a 255. Quindi la prima voce non è stata ignorata e ha avuto uno scopo. Quando qualcuno in seguito ha aggiunto il cast difensivo, il EOFvalore di -1 non avrebbe funzionato con quel cast, quindi ha aggiunto l'operatore condizionale per trattarlo in modo speciale.
Eric Postpischil,

3

_ctype_è un puntatore a un array globale di 257 byte. Non so a cosa _ctype_[0]serva. _ctype_[1]through _ctype_[256]_rappresenta le categorie di caratteri dei caratteri 0,…, 255 rispettivamente: _ctype_[c + 1]rappresenta la categoria del personaggio c. Questa è la stessa cosa che dire che _ctype_ + 1punta a una matrice di 256 caratteri in cui (_ctype_ + 1)[c]rappresenta la categoria del personaggio c.

(_ctype_ + 1)[(unsigned char)_c]non è una dichiarazione. È un'espressione che utilizza l'operatore di sottoscrizione di array. Sta accedendo alla posizione (unsigned char)_cdell'array che inizia da (_ctype_ + 1).

Il codice viene lanciato _cda inta unsigned charnon è strettamente necessario: le funzioni ctype accettano i valori char cast unsigned char( charè firmato su OpenBSD): una chiamata corretta è char c; … iscntrl((unsigned char)c). Hanno il vantaggio di garantire l'assenza di buffer overflow: se l'applicazione chiama iscntrlcon un valore che è al di fuori dell'intervallo unsigned chare non è -1, questa funzione restituisce un valore che potrebbe non essere significativo ma almeno non causerà un arresto anomalo o una perdita di dati privati ​​che si trovavano all'indirizzo esterno ai limiti dell'array. Il valore è anche corretto se la funzione viene chiamata char c; … iscntrl(c)fino a quando cnon è -1.

Il motivo del caso speciale con -1 è che lo è EOF. Molte funzioni C standard che operano su un char, ad esempio getchar, rappresentano il carattere come un intvalore che è il valore del carattere racchiuso in un intervallo positivo e usano il valore speciale EOF == -1per indicare che non è possibile leggere alcun carattere. Per funzioni come getchar, EOFindica la fine del file, da cui il nome e nd- o f- f ile. Eric Postpischil suggerisce che il codice era originariamente giusto return _ctype_[_c + 1], e probabilmente è giusto: _ctype_[0]sarebbe il valore per EOF. Questa implementazione più semplice produce un overflow del buffer se la funzione viene utilizzata in modo improprio, mentre l'implementazione corrente evita questo come discusso in precedenza.

Se vè il valore trovato nell'array, v & _Cverifica se il bit at 0x20è impostato in v. I valori nella matrice sono maschere delle categorie in cui si trova il carattere: _Cè impostato per i caratteri di controllo, _Uè impostato per le lettere maiuscole, ecc.


(_ctype_ + 1)[_c] sarebbe utilizzare l'indice di matrice corretta come specificato dallo standard C, perché è responsabilità dell'utilizzatore passare sia EOFo un unsigned charvalore. Il comportamento per altri valori non è definito dallo standard C. Il cast non serve per implementare il comportamento richiesto dallo standard C. Si tratta di una soluzione alternativa messa in guardia contro i bug causati da programmatori che passano erroneamente valori di caratteri negativi. Tuttavia, è incompleto o errato (e non può essere corretto) perché un valore di -1 carattere sarà necessariamente trattato come EOF.
Eric Postpischil,

Questo offre anche una spiegazione del + 1. Se la macro non contenesse in precedenza questa correzione difensiva, avrebbe potuto essere implementata semplicemente come ((_ctype_+1)[_c] & _C), avendo quindi una tabella indicizzata con i valori di pre-regolazione da -1 a 255. Quindi la prima voce non è stata ignorata e ha avuto uno scopo. Quando qualcuno in seguito ha aggiunto il cast difensivo, il EOFvalore di -1 non avrebbe funzionato con quel cast, quindi ha aggiunto l'operatore condizionale per trattarlo in modo speciale.
Eric Postpischil,

2

Inizierò con il passaggio 3:

incrementa l'indirizzo a cui punta il puntatore indefinito di 1

Il puntatore non è indefinito. È appena definito in qualche altra unità di compilazione. Questo è ciò che la externparte dice al compilatore. Pertanto, quando tutti i file sono collegati tra loro, il linker risolverà i riferimenti ad esso.

Quindi cosa indica?

Indica un array con informazioni su ciascun personaggio. Ogni personaggio ha la sua voce. Una voce è una rappresentazione bitmap di caratteristiche per il personaggio. Ad esempio: se il bit 5 è impostato, significa che il carattere è un carattere di controllo. Un altro esempio: se il bit 0 è impostato, significa che il carattere è un carattere superiore.

Quindi qualcosa del genere (_ctype_ + 1)['x']otterrà le caratteristiche che si applicano a 'x'. Quindi un bit per bit e viene eseguito per verificare se il bit 5 è impostato, ovvero verificare se si tratta di un carattere di controllo.

Il motivo per l'aggiunta di 1 è probabilmente che l'indice reale 0 è riservato per uno scopo speciale.


1

Tutte le informazioni qui si basano sull'analisi del codice sorgente (e dell'esperienza di programmazione).

La dichiarazione

extern const char *_ctype_;

dice al compilatore che c'è un puntatore a un const charposto chiamato _ctype_.

(4) Questo puntatore è accessibile come un array.

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

Il cast si (unsigned char)_cassicura che il valore dell'indice sia compreso nell'intervallo di un unsigned char(0..255).

L'aritmetica del puntatore _ctype_ + 1sposta efficacemente la posizione dell'array di 1 elemento. Non so perché abbiano implementato l'array in questo modo. Utilizzando l'intervallo_ctype_[1] ... _ctype[256]per i valori dei caratteri 0... 255lascia il valore _ctype_[0]inutilizzato per questa funzione. (L'offset di 1 potrebbe essere implementato in diversi modi alternativi.)

L'accesso alla matrice recupera un valore (di tipo char, per risparmiare spazio) utilizzando il valore del carattere come indice della matrice.

(5) L'operazione AND bit a bit estrae un singolo bit dal valore.

Apparentemente il valore dell'array viene utilizzato come campo bit in cui il bit 5 (contando da 0 a partire almeno il bit significativo, = 0x20) è un flag per "è un carattere di controllo". Quindi l'array contiene valori di campo bit che descrivono le proprietà dei caratteri.


Immagino che abbiano spostato il + 1puntatore per chiarire che stanno accedendo agli elementi 1..256anziché 1..255,0. _ctype_[1 + (unsigned char)_c]sarebbe stato equivalente a causa della conversione implicita in int. E _ctype_[(_c & 0xff) + 1]sarebbe stato ancora più chiaro e conciso.
cmaster - reintegra monica il

0

La chiave qui è capire cosa fa l'espressione (_ctype_ + 1)[(unsigned char)_c](che viene poi alimentata con bitwise e operazione, & 0x20per ottenere il risultato!

Risposta breve: restituisce l'elemento _c + 1dell'array puntato da_ctype_ .

Come?

Innanzitutto, anche se sembra che tu _ctype_sia indefinito , in realtà non lo è! L'intestazione dichiara come una variabile esterna, ma è definito (quasi certamente) in una delle librerie di runtime a cui è collegato il tuo programma quando lo costruisci.

Per illustrare come la sintassi corrisponde all'indicizzazione dell'array, prova a elaborare (anche compilando) il seguente programma breve:

#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;
}

Sentiti libero di chiedere ulteriori chiarimenti e / o spiegazioni.


0

Le funzioni dichiarate in ctype.haccettano oggetti del tipo int. Per i caratteri usati come argomenti, si presume che siano sottoposti al cast preliminare al tipounsigned char . Questo personaggio viene utilizzato come indice in una tabella che determina le caratteristiche del personaggio.

Sembra che il controllo _c == -1sia usato nel caso in cui _ccontenga il valore di EOF. In caso contrario, EOF_c viene eseguito il cast del tipo unsigned char utilizzato come indice nella tabella a cui punta l'espressione _ctype_ + 1. E se il bit specificato dalla maschera 0x20è impostato, il carattere è un simbolo di controllo.

Per capire l'espressione

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

tenere presente che la sottoscrizione di array è un operatore postfix definito come

postfix-expression [ expression ]

Non puoi scrivere come

_ctype_ + 1[(unsigned char)_c]

perché questa espressione è equivalente a

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

Quindi l'espressione _ctype_ + 1è racchiusa tra parentesi per ottenere un'espressione primaria.

Quindi in effetti hai

pointer[integral_expression]

che restituisce l'oggetto di un array in corrispondenza dell'indice che viene calcolato come espressione in integral_expressioncui si trova il puntatore (_ctype_ + 1)(gere viene utilizzato il puntatore arithmetuc) e integral_expressionche l'indice è l'espressione (unsigned char)_c.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.