7
votes

Est-il sécuritaire d'appeler les fonctions de avec des arguments de caractère?

Le langage de programmation C indique que les fonctions de suivent une exigence commune:

ISO C99, 7.4P1:

Dans tous les cas, l'argument est un int , la valeur qui doit être représentable en tant que non signé Char ou doit être égal à la valeur de la macro eof < / code>. Si l'argument a une autre valeur, le comportement est indéfini.

Cela signifie que le code suivant est dangereux: xxx

si ce code est exécuté sur une implémentation où Char a le Même espace de valeur que signé caractère et il y a un caractère avec une valeur négative dans la chaîne, ce code invoque comportement non défini . La version correcte est la suivante: xxx

Je vois néanmoins beaucoup d'exemples en C ++ qui ne se soucient pas de cette possibilité de comportement non défini. Ainsi y a-t-il quelque chose dans la norme C ++ qui garantit que le code ci-dessus ne conduira pas à un comportement non défini, ou tous les exemples faux?

[Mots-clés supplémentaires: CYPE CCTYPE Isalnum Isalpha Isblank Iscntrl Isdigit Islowwer isprint ispunct Isspace ispper isxdigit tolower]


4 commentaires

Vous demandez à propos de C ++ mais citant de C99?


Oh bien, C ++ 98 était avant C99. Néanmoins, le texte de C90 est presque identique, et C ++ 98 emprunte sa bibliothèque standard de C90, donc oui, je tiens à la norme C intentionnellement.


La signature de Char est spécifique au compilateur. Bien que j'en doute, peut-être que certains de ces "faux" exemples de projets forcent le compilateur à traiter des caractères non signés.


@CNICUTAR La norme C ++ 98 contient des détails sur . Suite aux normes C ++ ont supprimé la section entièrement en faveur de l'indiquant explicitement [cctype.syn] que l'on devrait se référer à la section 7.4 ISO C pour les définitions, dans laquelle l'ISO C est la dernière norme C au moment de la rédaction de la rédaction. (c.-à-d. C99 dans tous les cas). La citation de l'OP est donc la citation correcte (et uniquement), selon toutes les normes C ++ au cours des 20 dernières années. C'était également le cas il y a 10 ans lorsque l'OP a été posté. :)


3 Réponses :


1
votes

Parfois, la plupart des gens ont tort. Je pense que c'est alors ici. Cela dit qu'il n'y a rien pour arrêter une implémentation de bibliothèque standard définissant le comportement que la plupart des gens attendent. Donc, peut-être que c'est pourquoi la plupart des gens ne se soucient pas, car ils n'ont jamais vu un bug résultant de cette erreur.


10 commentaires

Comment sa version corrigée est-elle correcte? Casting La valeur négative au caractère non signé ne donnera rien de significatif!


Pourquoi pas? Si j'ai le personnage ä in Latin1 (ISO-8859-1), il est exprimé en tant que 228. Si mon caractère est signé, IST est stocké sous-28. Si je le jette pas non signé, ce sera à nouveau.


La valeur négative au caractère non signé est bien définie. La conversion originale d'un entier> = 128 pour signé de caractère est toutefois ub. Mais pas beaucoup de gens se soucient de cela. Néanmoins j'ai édité mon post.


@John: conversion de int à Char n'est pas UB, il donne simplement une valeur définie sur la mise en oeuvre lorsque la valeur de la source n'est pas dans la plage représente en tant que Char < / code>.


@John: Pourquoi avez-vous édité la dernière phrase de votre réponse d'origine? Y avait-il quelque chose de mal avec ça?


@Chals Bailey: Vous êtes correct bien sûr. Post modifié à nouveau. Ce genre de choses est très fastidieuse, tout le monde sait ce qui se produira réellement, il est dommage que la norme ne le garantit pas.


De Couse, toute implémentation dans laquelle la conversion d'un Char avec une valeur négative à un non signé Char ne donne pas une valeur non signée de la valeur qui représente Le même caractère que l'original Char est clairement écrou.


@glGL Si nous parlons en termes de programme de plateformes croisées, il est stocké uniquement sous-28 pour une représentation complémentaire TWO. Pour être portable, vous devez dire * (sans signé Char *) & s [index] de sorte que quelle que soit la valeur négative, que vous finissez par 228 sur la base de BitPattern.


Toute mise en oeuvre dans laquelle la plaine Char est signée et non twos-complément est absurde. Il existe de nombreux problèmes tels que "zéro négatif" existant, mais pas étant un terminateur à chaîne, et copier des octets comme une matrice de char devenant une opération à perte. La spécification est plutôt floue sur ces questions. Il serait donc vraiment invité à faire une telle mise en œuvre.


@R. -Complément maths sans difficulté.



2
votes

Pour ce que ça vaut, les compilateurs Solaris Studio (en utilisant STLPORT4 CODE>) sont une de ces suites compilatrice qui produisent ici un résultat inattendu. Compiler et exécuter ceci: xxx pré>

me donne: p> xxx pré>

pour référence: p> xxx pré> Bien sûr, ce comportement est documenté dans la norme C ++, mais c'est vraiment surprenant. P>


Edit: Comme il a été souligné que la version ci-dessus contenait un comportement non défini dans la tentative d'attribution de char ch = '\ xa1' code> en raison d'un débordement entier, voici une version qui évite et conserve toujours la même sortie: P>

kevin@solaris:~/scratch
$ CC whitespace.cpp && ./a.out
3


6 commentaires

Char a la mise en œuvre définie de la mise en œuvre. Si elle est signée et que vous stockez une valeur supérieure à 127, telle que 0xa1, vous invoquez un débordement entier, ce qui est un comportement indéfini. Ce bug UB arrive avant vous appelez la fonction CTYPE.H. Donc, cette réponse ne prouve rien, sauf qu'il est dangereux de déborder des entiers signés. Pas de nouvelles là-bas. Changer de caractère non signé et cela fonctionnera très bien.


Après la modification, lorsque vous avez défini -95 sur un caractère signé, celui-ci est converti en into lorsqu'il est passé à Isspace. Vous obtenez une conversion de lvalue de char sur int . Le signe est préservé. Donc, ce que ce code passe à la fonction est 0xFFFFFFFA1. Cette valeur n'est pas représentative en tant que caractère non signé, ni en tant que EOF, le code invoque UB. Encore une fois, le problème ne ment pas avec les fonctions CTYPE.H, mais avec le programmeur à l'aide du type Char pour stocker des entiers. Pourquoi réussiriez-vous -95 aux fonctions CTYPE dans un programme mondial réel, je n'ai aucune idée. Si vous faites des choses vraiment étranges, les choses étranges se produiront.


L'OP ne demandait pas si c'était un comportement indéfini (nous le savons). L'OP demandait s'il existe une implémentation qui vous donne un résultat surprenant lorsque vous invoquez ce comportement non défini. Les compilateurs Solaris Studio se produisent une de ces suites compilatrice qui vous donnent des résultats surprenants, alors que j'imagine d'autres compilateurs ( gcc , clang ) fait probablement la bonne chose et vous donner un résultat sain même s'il est ub.


@LUNDIN stockage des valeurs arbitraires dans un char objet pas Invoque un comportement non défini, voir 6.2.5p3 dans N1570.PDF.


@Rolandillig J'ai déjà répondu à cela dans un commentaire précédent. Vous ne pouvez pas stocker les valeurs qui ne correspondent pas, période.


Ah, dans le cas spécifique de la conversion d'affectation / Lvalue, il n'est pas nécessairement ub mais implanté comportement défini. La partie correspondante serait 6.3.1.3/3 "sinon, le nouveau type est signé et la valeur ne peut être représentée; soit le résultat est défini par la mise en œuvre ou un signal défini par la mise en œuvre est soulevé." Peu importe, ne le faites pas.



0
votes

L'historique derrière le type Char est qu'il était à l'origine le type utilisé pour décrire les caractères ASCII 7 bits. Dans le même temps, c Manque de type integer 8 bits séparé. Donc, dans les jours de pré-standard des années quatre-vingt, certains compilateurs fabriqués sans signé - car il n'a pas de sens d'avoir des indices négatifs dans une table de symboles, tandis que d'autres compilateurs ont fait char Signé, pour le rendre cohérent avec tous les autres types entier.

Quand le temps est venu de normaliser c, les deux versions existaient. Malheureusement, le Comité a décidé de le laisser rester de cette façon, laissant la décision au compilateur. Au lieu de cela, ils ont ajouté deux autres types: signé Char et non signé Char . SIGNÉ CHAR fait partie des types d'entiers signés, Unsigned Char fait partie des types d'entiers non signés et Char fait partie de l'un ni l'autre Doit avoir la même représentation que l'un ou l'autre signé caractère ou non signé Char . (Ceci est tout décrit dans C11 6.2.5)

notamment, Char n'a jamais été quelque chose que 8 bits sur toutes les implémentations connues, économisez de certains DSP de boules d'odeur exotiques qui fonctionnaient avec 16 bitts. Lorsque des tables de symboles "étendues" ont été utilisées, la mise en œuvre est passée de 7 à 8 caractères bits, soit wchar_t a été utilisé. Veuillez noter que wchar_t est dans la langue C depuis le début, supposant ainsi que Char était à un moment donné pour des éléments tels que UTF8 est probablement incorrect (bien possible théoriquement).

maintenant si Char est signé et que vous stockez une valeur supérieure à char_max ou plus petit que char_min à l'intérieur, vous invoquez un comportement non défini, conformément au C11 6.5 §5. Point final. Donc, si vous avez un tableau de Char et n'importe quel article à l'intérieur, il enfreint les limites de type, vous avez déjà un comportement indéfini là-bas. Même si les types de caractères doivent piéger des représentations, un comportement indéfini pourrait entraîner une mauvaise conduite du code d'une autre manière, telles que des optimisations incorrectes.

Les fonctions CTYPE.H Autoriser EOF comme paramètre, mais autrement se comporter comme si vous travaillez avec des types de caractères, même si le paramètre est int pour autoriser eof . Le texte du 7.4 §1 dit principalement que "si vous passez du hasard int à cette fonction, qui n'est ni de la même représentation qu'un caractère, ni EOF, le comportement n'est pas défini" .

Mais si vous passez un char où vous avez déjà invoqué SIGNÉ ENTREGER Overflow / Integer, vous avez déjà un comportement non défini avant d'appeler la fonction - cela n'a rien à voir avec les fonctions CTYPE.H ou toute autre fonction. Ainsi, votre hypothèse que la fonction postée "supérieure" est dangereuse est incorrecte - ce code n'est pas différent de tout autre code à l'aide du type char .

Un exemple de comportement non défini causé par les restrictions CITE CITE.H dans 7.4 serait plutôt quelque chose comme Toupper (666) . .


6 commentaires

C11 6.2.5P3 semble permettre de sauvegarder des "caractères" arbitraires dans un objet de type de caractère, qui contredire le comportement non défini de 6.5P5.


Dans votre devis de 7.4p1, il devrait être "non signé de caractère" au lieu de "caractère". Sur une plate-forme où char_bit est 8 et Char est signé, appelant TUPPER (192) n'appuie pas un comportement non défini. (Votre devis suggère le contraire.)


@Rolandillig No, 6.2.5p3 Points sur le jeu de caractères d'exécution de base qui est un terme formel défini dans 5.2.1. Signifiant à peu près l'alphabet anglais en majuscules et minuscules, plus des chiffres et des caractères de ponctuation, etc., ce qui signifie à son tour 7 bits ASCII - à tout prix, le jeu de caractères d'exécution de base peut correspondre à 7 bits. Au-delà de cela, il est indiqué que d'autres caractères peuvent être stockés de manière définie par la mise en œuvre " mais doivent figurer dans la plage de valeurs pouvant être représentées dans ce type ". Ce qui signifie que vous n'êtes toujours pas autorisé à débordement / débordement.


@Rolandillig concernant 7.4, je ne l'ai pas cité. Oui, Toupper (192) va bien, je n'ai pas dit autrement. Mais CHAR CH = 192; est un comportement potentiellement indéfini, peu importe si vous passez le caractère à une fonction ou non. L'ensemble de cette réponse consiste à souligner qu'il n'y a que deux problèmes ici: 1) Overflowing / Endfloquant un char ou tout autre entiers signé est UB. 2) Transmettre une valeur orale aléatoire sur les fonctions CTYPE.H est UB. Le code de votre question n'est-il pas non plus, il est donc parfaitement parfait et sûr.


Dans votre réponse, vous avez écrit: "Maintenant si Char est signé et que vous stockez une valeur plus grande que char_max ou plus petit que char_min à l'intérieur, Vous invoquez un comportement non défini, conformément au C11 6.5 §5. Période. " - Je suis en désaccord avec cette déclaration. La partie pertinente de la norme dans ce cas est §6.3.1.3 ¶3 , qui indique que le résultat est défini par la mise en oeuvre ou un signal défini par la mise en œuvre est soulevé. Dans votre commentaire à l'une des autres réponses, vous l'avez signalé vous-même, mais il semble que vous ne mettez pas à jour votre propre réponse par la suite.


@Andreaswenzel La partie que vous citez s'applique lorsque les conversions ont lieu. Mais vous pouvez stocker une valeur dans une variable sans appeler des conversions. Par exemple, à travers l'accès LValue: * (sans signé Char *) pointer_to_char = char_max + 1; . Ceci est par exemple ce qui se passe si vous écrivez le mauvais spécificateur de conversion sur scanf .