Ce code source allume une chaîne en C. Comment fait-il cela?

106

Je lis un code d'émulateur et j'ai contré quelque chose de vraiment étrange:

switch (reg){
    case 'eax':
    /* and so on*/
}

Comment est-ce possible? Je pensais que vous ne pouviez que switchsur les types intégraux. Y a-t-il des tromperies macro?

Ian Colton
la source
29
ce n'est pas la chaîne 'eax'et elle énumère la valeur entière constante
P__J__
12
Guillemets simples, pas doubles. Une constante de caractère est promue vers int, donc c'est légal. Cependant, la valeur d'une constante à plusieurs caractères est définie par l'implémentation, de sorte que le code peut ne pas fonctionner comme prévu sur un autre compilateur. Par exemple, eaxpeut - être 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, ou autre chose.
Davislor
2
@Davislor: étant donné le nom de la variable "reg", et le fait qu'eax est un registre x86, je suppose que le comportement défini par l'implémentation était censé être correct, car il est le même partout où il est utilisé dans le code. Aussi longtemps que 'eax' != 'ebx', bien sûr, cela ne fait échouer qu'un ou deux de vos exemples. Bien qu'il puisse y avoir du code quelque part qui en fait suppose *(int*)("eax") == 'eax', et donc échoue la plupart de vos exemples.
Steve Jessop
2
@SteveJessop Je ne suis pas en désaccord avec ce que vous dites, mais il y a un danger réel que quelqu'un puisse essayer de compiler le code sur un compilateur différent, même pour la même architecture, et obtenir un comportement différent. Par exemple, 'eax'pourrait comparer égal à 'ebx'ou à 'ax', et l'instruction switch ne fonctionnerait pas comme prévu.
Davislor
1
Tout ce mystère aurait été rapidement dissipé si vous aviez recherché / montré le type de données de reg.
le

Réponses:

146

(Vous seul pouvez répondre à la partie "macros tricherie" - à moins que vous ne colliez plus de code. Mais il n'y a pas grand-chose ici pour les macros sur lesquelles travailler - formellement, vous n'êtes pas autorisé à redéfinir les mots-clés ; le comportement en ce sens n'est pas défini.)

Afin d'atteindre la lisibilité du programme, le développeur intelligent exploite le comportement défini par l'implémentation . 'eax'n'est pas une chaîne, mais une constante à plusieurs caractères . Notez très attentivement les caractères de guillemets simples autour eax. Très probablement, cela vous donne un intdans votre cas qui est unique à cette combinaison de caractères. (Assez souvent, chaque caractère occupe 8 bits dans un 32 bits int). Et tout le monde sait que vous pouvez switchsur un int!

Enfin, une référence standard:

La norme C99 dit:

6.4.4.4p10: "La valeur d'une constante de caractère entier contenant plus d'un caractère (par exemple 'ab'), ou contenant un caractère ou une séquence d'échappement qui ne correspond pas à un caractère d'exécution à un octet, est définie par l'implémentation. "

Bathsheba
la source
55
Juste au cas où quelqu'un verrait cela et paniquerait, "défini par l'implémentation" est nécessaire pour fonctionner et être documenté par votre compilateur d'une manière appropriée (le standard n'exige pas que le comportement soit intuitif ou que la documentation soit bonne, mais ...). C'est "sûr" à utiliser pour un codeur qui comprend parfaitement ce qu'il écrit, par opposition à "indéfini".
Leushenko
7
@Justin Bien que cela soit possible, ce serait assez pervers. S'il ne fait pas ce que la réponse suggère est le plus probable, la possibilité suivante est probablement qu'il utilise simplement le premier caractère et ignore le reste.
Barmar
5
@ZanLynx Je ne suis pas sûr, mais je pense que la fonctionnalité est bien antérieure à l'Unicode et aux autres normes MBCS. Les «nombres magiques» qui ressemblent à du texte dans les vidages de mémoire et les identifiants de bloc de format de fichier de style RIFF ont été les premières applications que je connaisse.
Russell Borogove
16
@ jpmc26 Ce n'est pas un comportement indéfini, il est défini par l'implémentation. Donc, à moins que la documentation du compilateur mentionne des démons, votre nez est en sécurité.
Barmar
7
@ZanLynx: J'ai bien peur que l'intention originale soit antérieure à Unicode, UTF-8 et tout encodage de caractères multi-octets de près de 20 ans. Les constantes à plusieurs caractères n'étaient qu'un moyen pratique d'exprimer des entiers représentant des groupes de 2, 3 ou 4 octets (selon la taille des octets et des int). Les incohérences entre les implémentations et les architectures ont conduit le comité à déclarer cela comme défini par l'implémentation , ce qui signifie qu'il n'y a pas de moyen portable de calculer la valeur de 'ab'from 'a'et 'b'.
chqrlie
45

Selon la norme C (6.8.4.2 L'instruction switch)

3 L'expression de chaque étiquette de cas doit être une expression constante entière ...

et (6.6 Expressions constantes)

6 Une expression de constante entière doit être de type entier et ne doit avoir que des opérandes qui sont des constantes entières, des constantes d'énumération, des constantes de caractères , des expressions de taille dont les résultats sont des constantes entières et des constantes flottantes qui sont les opérandes immédiats de casts. Les opérateurs de conversion dans une expression constante entière ne doivent convertir que les types arithmétiques en types entiers, sauf dans le cadre d'un opérande en opérateur sizeof.

Maintenant qu'est-ce que c'est 'eax'?

La norme C (6.4.4.4 Constantes de caractères)

2 Une constante de caractère entier est une séquence d'un ou plusieurs caractères multi-octets entre guillemets simples , comme dans 'x' ...

Ainsi 'eax'est une constante de caractère entier selon le paragraphe 10 de la même section

  1. ... La valeur d'une constante de caractère entier contenant plus d'un caractère (par exemple, 'ab'), ou contenant un caractère ou une séquence d'échappement qui ne correspond pas à un caractère d'exécution à un octet, est définie par l'implémentation.

Ainsi, selon la première citation mentionnée, il peut s'agir d'un opérande d'une expression constante entière qui peut être utilisée comme étiquette de cas.

Faites attention au fait qu'une constante de caractère (entre guillemets simples) a un type intet n'est pas la même chose qu'une chaîne littérale (une séquence de caractères entre guillemets) qui a un type de tableau de caractères.

Vlad de Moscou
la source
12

Comme d'autres l'ont dit, il s'agit d'une intconstante et sa valeur réelle est définie par l'implémentation.

Je suppose que le reste du code ressemble à quelque chose comme

if (SOMETHING)
    reg='eax';
...
switch (reg){
    case 'eax':
    /* and so on*/
}

Vous pouvez être sûr que «eax» dans la première partie a la même valeur que «eax» dans la deuxième partie, donc tout fonctionne, non? ... faux.

Dans un commentaire, @Davislor énumère quelques valeurs possibles pour 'eax':

... 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, ou autre chose

Remarquez la première valeur potentielle? C'est juste 'e', en ignorant les deux autres personnages. Le problème est le programme utilise probablement 'eax', 'ebx'et ainsi de suite. Si toutes ces constantes ont la même valeur que 'e'vous vous retrouvez avec

switch (reg){
    case 'e':
       ...
    case 'e':
       ...
    ...
}

Cela n'a pas l'air trop beau, n'est-ce pas?

La bonne partie de "défini par l'implémentation" est que le programmeur peut vérifier la documentation de son compilateur et voir s'il fait quelque chose de sensé avec ces constantes. Si c'est le cas, gratuitement à la maison.

La mauvaise partie est qu'un autre pauvre type peut prendre le code et essayer de le compiler en utilisant un autre compilateur. Erreur de compilation instantanée. Le programme n'est pas portable.

Comme @zwol l'a souligné dans les commentaires, la situation n'est pas aussi mauvaise que je le pensais, dans le mauvais cas, le code ne se compile pas. Cela vous donnera au moins un nom de fichier et un numéro de ligne exacts pour le problème. Pourtant, vous n'aurez pas de programme de travail.

Stig Hemmer
la source
1
autre qu'une certaine forme de assert('eax' != 'ebx'); //if this fails you can't compile the code because...y a-t-il quelque chose que l'auteur original pourrait faire pour empêcher d'autres échecs du compilateur sans remplacer entièrement la construction>
Dan Is Fiddling By Firelight
6
Deux étiquettes de cas avec la même valeur constituent une violation de contrainte (6.8.4.2p3: "... aucune des expressions de constante de cas dans la même instruction de commutation ne doit avoir la même valeur après la conversion") donc, tant que tout le code traite les valeurs de ces constantes comme opaques, il est garanti que cela fonctionne ou que la compilation échoue.
zwol
Le pire, c'est que le pauvre camarade qui compile sur un autre compilateur ne verra probablement aucune erreur de compilation (activer ints est très bien); au lieu de cela, des erreurs d' exécution
surviendront
1

Le fragment de code utilise une bizarrerie historique appelée constante de caractère multi-caractères , également appelée multi-caractères .

'eax' est une constante entière dont la valeur est définie par l'implémentation.

Voici une page intéressante sur les multi-caractères et comment ils peuvent être utilisés mais ne devraient pas:

http://www.zipcon.net/~swhite/docs/computers/languages/c_multi-char_const.html


En regardant en arrière plus loin dans le rétroviseur, voici comment le manuel C original de Dennis Ritchie du bon vieux temps ( https://www.bell-labs.com/usr/dmr/www/cman.pdf ) spécifiait les constantes de caractères .

2.3.2 Constantes de caractères

Une constante de caractère comprend 1 ou 2 caractères entre guillemets simples '' '''. Dans une constante de caractère, un guillemet simple doit être précédé d'une barre oblique inverse '' \''. Certains caractères non graphiques, et «» \lui-même, peuvent être échappés selon le tableau suivant:

    BS \b
    NL \n
    CR \r
    HT \t
    ddd \ddd
    \ \\

L'échappement '' \ddd'' se compose de la barre oblique inverse suivie de 1, 2 ou 3 chiffres octaux qui sont utilisés pour spécifier la valeur du caractère souhaité. Un cas particulier de cette construction est '' \0'' (non suivi d'un chiffre) qui indique un caractère nul.

Les constantes de caractères se comportent exactement comme des entiers (pas, en particulier, comme des objets de type caractère). Conformément à la structure d'adressage du PDP-11, une constante de caractère de longueur 1 a le code du caractère donné dans l'octet de poids faible et 0 dans l'octet de poids fort; une constante de caractère de longueur 2 a le code du premier caractère de l'octet de poids faible et celui du deuxième caractère de l'octet de poids fort. Les constantes de caractère comportant plusieurs caractères sont intrinsèquement dépendantes de la machine et doivent être évitées.

La dernière phrase est tout ce dont vous devez vous souvenir à propos de cette curieuse construction: les constantes de caractère avec plus d'un caractère sont intrinsèquement dépendantes de la machine et doivent être évitées.

chqrlie
la source