Je veux comprendre le code suivant:
//...
#define _C 0x20
extern const char *_ctype_;
//...
__only_inline int iscntrl(int _c)
{
return (_c == -1 ? 0 : ((_ctype_ + 1)[(unsigned char)_c] & _C));
}
Il provient du fichier ctype.h du code source du système d'exploitation obenbsd. Cette fonction vérifie si un caractère est un caractère de contrôle ou une lettre imprimable à l'intérieur de la plage ascii. Voici ma chaîne de pensée actuelle:
- iscntrl ('a') est appelé et 'a' est converti en sa valeur entière
- vérifiez d'abord si _c est -1 puis retournez 0 sinon ...
- incrémenter l'adresse vers laquelle le pointeur non défini pointe de 1
- déclarer cette adresse comme un pointeur sur un tableau de longueur (caractère non signé) ((int) 'a')
- appliquer l'opérateur au niveau du bit et à _C (0x20) et au tableau (???)
D'une manière ou d'une autre, étrangement, cela fonctionne et à chaque fois que 0 est renvoyé, le caractère _c donné n'est pas un caractère imprimable. Sinon, lorsqu'elle est imprimable, la fonction renvoie simplement une valeur entière qui ne présente aucun intérêt particulier. Mon problème de compréhension se trouve aux étapes 3, 4 (un peu) et 5.
Merci pour toute aide.
_ctype_
est essentiellement un tableau de bitmasks. Il est indexé par le caractère qui nous intéresse. Contiendrait donc_ctype_['A']
des bits correspondant à "alpha" et "majuscule",_ctype_['a']
contiendrait des bits correspondant à "alpha" et "minuscule",_ctype_['1']
contiendrait un bit correspondant à "chiffre", etc. On dirait que0x20
c'est le bit correspondant à "contrôle" . Mais pour une raison quelconque, le_ctype_
tableau est décalé de 1, donc les bits de'a'
sont vraiment dedans_ctype_['a'+1]
. (C'était probablement pour le laisser fonctionnerEOF
même sans le test supplémentaire.)(unsigned char)
est de prendre en compte la possibilité que les personnages soient signés et négatifs.Réponses:
_ctype_
semble être une version interne restreinte de la table des symboles et je suppose+ 1
que c'est qu'ils n'ont pas pris la peine de sauvegarder l'index0
car celui-ci n'est pas imprimable. Ou peut-être qu'ils utilisent une table indexée 1 au lieu de l'index 0 comme c'est la coutume en C.La norme C le dicte pour toutes les fonctions ctype.h:
En parcourant le code étape par étape:
int iscntrl(int _c)
Lesint
types sont vraiment des caractères, mais toutes les fonctions ctype.hEOF
doivent être gérées , donc elles doivent l'êtreint
.-1
est une vérification contreEOF
, car elle a la valeur-1
._ctype+1
est l'arithmétique du pointeur pour obtenir l'adresse d'un élément de tableau.[(unsigned char)_c]
est simplement un accès au tableau de ce tableau, où le cast est là pour appliquer l'exigence standard du paramètre représentable en tant queunsigned char
. Notez que celachar
peut en fait contenir une valeur négative, c'est donc une programmation défensive. Le résultat de l'[]
accès au tableau est un seul caractère de leur table de symboles interne.&
masquage est là pour obtenir un certain groupe de caractères de la table des symboles. Apparemment, tous les caractères avec le bit 5 défini (masque 0x20) sont des caractères de contrôle. Il n'y a aucun sens à cela sans regarder la table.la source
unsigned char
. La norme exige que la valeur déjà * soit représentable commeunsigned char
, ou égaleEOF
, lorsque la routine est appelée. La distribution ne sert que de programmation «défensive»: Correction de l'erreur d'un programmeur qui passe un signéchar
(ou unsigned char
) quand il incombait à eux de transmettre uneunsigned char
valeur lors de l'utilisation d'unectype.h
macro. Il convient de noter que cela ne peut pas corriger l'erreur lorsqu'unechar
valeur de -1 est passée dans une implémentation qui utilise -1 pourEOF
.+ 1
. Si la macro ne contenait pas auparavant cet ajustement défensif, alors il aurait pu être implémenté simplement comme((_ctype_+1)[_c] & _C)
, ayant ainsi un tableau indexé avec les valeurs de pré-ajustement -1 à 255. Ainsi, la première entrée n'a pas été ignorée et a servi un but. Quand quelqu'un a ajouté plus tard le casting défensif, laEOF
valeur de -1 ne fonctionnerait pas avec ce casting, ils ont donc ajouté l'opérateur conditionnel pour le traiter spécialement._ctype_
est un pointeur vers un tableau global de 257 octets. Je ne sais pas à quoi ça_ctype_[0]
sert._ctype_[1]
à_ctype_[256]_
représentent les catégories de caractères des caractères 0,…, 255 respectivement:_ctype_[c + 1]
représente la catégorie du caractèrec
. C'est la même chose que de dire qui_ctype_ + 1
pointe vers un tableau de 256 caractères où(_ctype_ + 1)[c]
représente la catégorie du caractèrec
.(_ctype_ + 1)[(unsigned char)_c]
n'est pas une déclaration. C'est une expression utilisant l'opérateur d'indice de tableau. C'est l'accès à la position(unsigned char)_c
du tableau qui commence à(_ctype_ + 1)
.Le code transtypé
_c
deint
àunsigned char
n'est pas strictement nécessaire: les fonctions ctype prennent les valeurs char converties enunsigned char
(char
est signé sur OpenBSD): un appel correct l'estchar c; … iscntrl((unsigned char)c)
. Ils ont l'avantage de garantir qu'il n'y a pas de dépassement de tampon: si l'application appelleiscntrl
avec une valeur qui est en dehors de la plage deunsigned char
et n'est pas -1, cette fonction renvoie une valeur qui peut ne pas être significative mais au moins ne provoquera pas un crash ou une fuite de données privées qui se trouvait à l'adresse en dehors des limites du tableau. La valeur est même correcte si la fonction est appelée tantchar c; … iscntrl(c)
quec
n'est pas -1.La raison du cas spécial avec -1 est que c'est le cas
EOF
. De nombreuses fonctions C standard qui opèrent surchar
, par exemplegetchar
, représentent le caractère comme uneint
valeur qui est la valeur char enveloppée dans une plage positive et utilisent la valeur spécialeEOF == -1
pour indiquer qu'aucun caractère n'a pu être lu. Pour des fonctions telles quegetchar
,EOF
indique la fin du fichier, d' où le nom e ND- o F- f ile. Eric Postpischil suggère que le code était à l'origine justereturn _ctype_[_c + 1]
, et c'est probablement vrai:_ctype_[0]
serait la valeur pour EOF. Cette implémentation plus simple donne lieu à un débordement de tampon si la fonction est mal utilisée, tandis que l'implémentation actuelle évite cela comme discuté ci-dessus.Si
v
est la valeur trouvée dans le tableau,v & _C
teste si le bit at0x20
est défini dansv
. Les valeurs du tableau sont des masques des catégories dans lesquelles se trouve le caractère:_C
est défini pour les caractères de contrôle,_U
est défini pour les lettres majuscules, etc.la source
(_ctype_ + 1)[_c]
serait utiliser l'index de tableau correct tel que spécifié par la norme C, car il est de la responsabilité de l'utilisateur de passer soitEOF
ou uneunsigned char
valeur. Le comportement des autres valeurs n'est pas défini par la norme C. Le cast ne sert pas à implémenter le comportement requis par la norme C. Il s'agit d'une solution de contournement mise en place pour se prémunir contre les bogues provoqués par des programmeurs passant incorrectement des valeurs de caractères négatives. Cependant, il est incomplet ou incorrect (et ne peut pas être corrigé) car une valeur de caractère -1 sera nécessairement traitée commeEOF
.+ 1
. Si la macro ne contenait pas auparavant cet ajustement défensif, alors il aurait pu être implémenté simplement comme((_ctype_+1)[_c] & _C)
, ayant ainsi un tableau indexé avec les valeurs de pré-ajustement -1 à 255. Ainsi, la première entrée n'a pas été ignorée et a servi un but. Quand quelqu'un a ajouté plus tard le casting défensif, laEOF
valeur de -1 ne fonctionnerait pas avec ce casting, ils ont donc ajouté l'opérateur conditionnel pour le traiter spécialement.Je vais commencer par l'étape 3:
Le pointeur est pas indéfini. Il est juste défini dans une autre unité de compilation. C’est ce que
extern
partie dit au compilateur. Ainsi, lorsque tous les fichiers sont liés ensemble, l'éditeur de liens résout les références.Alors à quoi cela fait-il référence?
Il pointe vers un tableau contenant des informations sur chaque caractère. Chaque personnage a sa propre entrée. Une entrée est une représentation bitmap des caractéristiques du personnage. Par exemple: si le bit 5 est activé, cela signifie que le caractère est un caractère de contrôle. Autre exemple: si le bit 0 est défini, cela signifie que le caractère est un caractère supérieur.
Donc, quelque chose comme
(_ctype_ + 1)['x']
obtiendra les caractéristiques qui s'appliquent à'x'
. Ensuite, un bit et est effectué pour vérifier si le bit 5 est activé, c'est-à-dire vérifier s'il s'agit d'un caractère de contrôle.La raison de l'ajout de 1 est probablement que l'index réel 0 est réservé à un usage spécial.
la source
Toutes les informations ici sont basées sur l'analyse du code source (et l'expérience de programmation).
La déclaration
indique au compilateur qu'il existe un pointeur vers un
const char
endroit nommé_ctype_
.(4) Ce pointeur est accessible sous forme de tableau.
Le transtypage
(unsigned char)_c
s'assure que la valeur d'index est dans la plage d'ununsigned char
(0..255).L'arithmétique du pointeur
_ctype_ + 1
décale efficacement la position du tableau d'un élément. Je ne sais pas pourquoi ils ont implémenté le tableau de cette façon. L'utilisation de la plage_ctype_[1]
.._ctype[256]
pour les valeurs de caractère0
..255
laisse la valeur_ctype_[0]
inutilisée pour cette fonction. (Le décalage de 1 peut être implémenté de plusieurs manières différentes.)L'accès au tableau récupère une valeur (de type
char
, pour économiser de l'espace) en utilisant la valeur de caractère comme index de tableau.(5) L'opération ET au niveau du bit extrait un seul bit de la valeur.
Apparemment, la valeur du tableau est utilisée comme un champ de bits où le bit 5 (en comptant à partir de 0 en commençant au moins le bit significatif, =
0x20
) est un drapeau pour "est un caractère de contrôle". Ainsi, le tableau contient des valeurs de champ de bits décrivant les propriétés des caractères.la source
+ 1
pointeur pour indiquer clairement qu'ils accèdent aux éléments1..256
au lieu de1..255,0
._ctype_[1 + (unsigned char)_c]
aurait été équivalent en raison de la conversion implicite enint
. Et_ctype_[(_c & 0xff) + 1]
aurait été encore plus clair et concis.La clé ici est de comprendre ce que fait l'expression
(_ctype_ + 1)[(unsigned char)_c]
(qui est ensuite alimentée au niveau du bit et de l' opération,& 0x20
pour obtenir le résultat!Réponse courte: elle renvoie l'élément
_c + 1
du tableau pointé par_ctype_
.Comment?
Premièrement, bien que vous sembliez penser que ce
_ctype_
n'est pas défini, ce n'est pas le cas! L'en-tête le déclare comme une variable externe - mais il est défini dans (presque certainement) l'une des bibliothèques d'exécution avec lesquelles votre programme est lié lorsque vous le créez.Pour illustrer comment la syntaxe correspond à l'indexation des tableaux, essayez de travailler (même en compilant) le programme court suivant:
N'hésitez pas à demander des éclaircissements et / ou des explications supplémentaires.
la source
Les fonctions déclarées dans
ctype.h
acceptent les objets de typeint
. Pour les caractères utilisés comme arguments, il est supposé qu'ils sont préalablement convertis en typeunsigned char
. Ce caractère est utilisé comme index dans une table qui détermine la caractéristique du caractère.Il semble que la vérification
_c == -1
soit utilisée au cas où le_c
contient la valeur deEOF
. Si ce n'est pasEOF
le cas, _c est converti en le type unsigned char utilisé comme index dans la table pointée par l'expression_ctype_ + 1
. Et si le bit spécifié par le masque0x20
est défini, le caractère est un symbole de contrôle.Pour comprendre l'expression
prendre en compte que l'indice de tableau est un opérateur suffixe défini comme
Vous ne pouvez pas écrire comme
parce que cette expression est équivalente à
Ainsi, l'expression
_ctype_ + 1
est placée entre parenthèses pour obtenir une expression principale.Donc en fait vous avez
qui donne l'objet d'un tableau à l'index qui est calculé comme l'expression
integral_expression
où le pointeur est(_ctype_ + 1)
(gere est utilisé le pointeur arithmetuc) etintegral_expression
qui est l'index est l'expression(unsigned char)_c
.la source