Pointeur C vers la déclaration du tableau avec bit à bit et opérateur

9

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:

  1. iscntrl ('a') est appelé et 'a' est converti en sa valeur entière
  2. vérifiez d'abord si _c est -1 puis retournez 0 sinon ...
  3. incrémenter l'adresse vers laquelle le pointeur non défini pointe de 1
  4. déclarer cette adresse comme un pointeur sur un tableau de longueur (caractère non signé) ((int) 'a')
  5. 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.

accentWool
la source
1
_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 que 0x20c'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 fonctionner EOFmême sans le test supplémentaire.)
Steve Summit
Le casting (unsigned char)est de prendre en compte la possibilité que les personnages soient signés et négatifs.
Steve Summit

Réponses:

3

_ctype_semble être une version interne restreinte de la table des symboles et je suppose + 1que 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:

Dans tous les cas, l'argument est un int, dont la valeur doit être représentable en tant que unsigned charou doit être égale à la valeur de la macroEOF

En parcourant le code étape par étape:

  • int iscntrl(int _c)Les inttypes sont vraiment des caractères, mais toutes les fonctions ctype.h EOFdoivent être gérées , donc elles doivent l'être int.
  • La vérification contre -1est une vérification contre EOF, 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 que unsigned char. Notez que cela charpeut 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.
  • le & 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.
  • Tout ce qui a le bit 5 défini renverra la valeur masquée par 0x20, qui est une valeur non nulle. Cela satisfait l'exigence de la fonction retournant non nul en cas de booléen true.
Lundin
la source
Il n'est pas correct que la fonte satisfasse à l'exigence standard que la valeur soit représentable en tant que unsigned char. La norme exige que la valeur déjà * soit représentable comme unsigned char, ou égale EOF, 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 un signed char) quand il incombait à eux de transmettre une unsigned charvaleur lors de l'utilisation d'une ctype.hmacro. Il convient de noter que cela ne peut pas corriger l'erreur lorsqu'une charvaleur de -1 est passée dans une implémentation qui utilise -1 pour EOF.
Eric Postpischil
Cela offre également une explication de la + 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, la EOFvaleur de -1 ne fonctionnerait pas avec ce casting, ils ont donc ajouté l'opérateur conditionnel pour le traiter spécialement.
Eric Postpischil
3

_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_ + 1pointe vers un tableau de 256 caractères où (_ctype_ + 1)[c]représente la catégorie du caractère c.

(_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)_cdu tableau qui commence à (_ctype_ + 1).

Le code transtypé _cde intà unsigned charn'est pas strictement nécessaire: les fonctions ctype prennent les valeurs char converties en unsigned char( charest signé sur OpenBSD): un appel correct l'est char 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 appelle iscntrlavec une valeur qui est en dehors de la plage de unsigned charet 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 tant char c; … iscntrl(c)que cn'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 sur char, par exemple getchar, représentent le caractère comme une intvaleur 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 que getchar, EOFindique la fin du fichier, d' où le nom e ND- o F- f ile. Eric Postpischil suggère que le code était à l'origine juste return _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 & _Cteste si le bit at 0x20est défini dans v. Les valeurs du tableau sont des masques des catégories dans lesquelles se trouve le caractère: _Cest défini pour les caractères de contrôle, _Uest défini pour les lettres majuscules, etc.

Gilles 'SO- arrête d'être méchant'
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 soit EOFou une unsigned charvaleur. 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 comme EOF.
Eric Postpischil
Cela offre également une explication de la + 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, la EOFvaleur de -1 ne fonctionnerait pas avec ce casting, ils ont donc ajouté l'opérateur conditionnel pour le traiter spécialement.
Eric Postpischil
2

Je vais commencer par l'étape 3:

incrémenter l'adresse du pointeur non défini pointe de 1

Le pointeur est pas indéfini. Il est juste défini dans une autre unité de compilation. C’est ce queextern 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.

4386427
la source
1

Toutes les informations ici sont basées sur l'analyse du code source (et l'expérience de programmation).

La déclaration

extern const char *_ctype_;

indique au compilateur qu'il existe un pointeur vers un const charendroit nommé _ctype_.

(4) Ce pointeur est accessible sous forme de tableau.

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

Le transtypage (unsigned char)_cs'assure que la valeur d'index est dans la plage d'un unsigned char(0..255).

L'arithmétique du pointeur _ctype_ + 1dé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ère 0.. 255laisse 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.

Bodo
la source
Je suppose qu'ils ont déplacé le + 1pointeur pour indiquer clairement qu'ils accèdent aux éléments 1..256au lieu de 1..255,0. _ctype_[1 + (unsigned char)_c]aurait été équivalent en raison de la conversion implicite en int. Et _ctype_[(_c & 0xff) + 1]aurait été encore plus clair et concis.
cmaster - réintègre monica
0

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, & 0x20pour obtenir le résultat!

Réponse courte: elle renvoie l'élément _c + 1du 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:

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

N'hésitez pas à demander des éclaircissements et / ou des explications supplémentaires.

Adrian Mole
la source
0

Les fonctions déclarées dans ctype.hacceptent les objets de type int. Pour les caractères utilisés comme arguments, il est supposé qu'ils sont préalablement convertis en type unsigned 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 == -1soit utilisée au cas où le _ccontient la valeur de EOF. Si ce n'est pas EOFle 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 masque 0x20est défini, le caractère est un symbole de contrôle.

Pour comprendre l'expression

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

prendre en compte que l'indice de tableau est un opérateur suffixe défini comme

postfix-expression [ expression ]

Vous ne pouvez pas écrire comme

_ctype_ + 1[(unsigned char)_c]

parce que cette expression est équivalente à

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

Ainsi, l'expression _ctype_ + 1est placée entre parenthèses pour obtenir une expression principale.

Donc en fait vous avez

pointer[integral_expression]

qui donne l'objet d'un tableau à l'index qui est calculé comme l'expression integral_expressionoù le pointeur est (_ctype_ + 1)(gere est utilisé le pointeur arithmetuc) et integral_expressionqui est l'index est l'expression (unsigned char)_c.

Vlad de Moscou
la source