Quel est l'opérateur >>> = en C?

294

Donné par un collègue comme un casse-tête, je ne peux pas comprendre comment ce programme C se compile et s'exécute réellement. Qu'est-ce que cet >>>=opérateur et l'étrange 1P1littéral? J'ai testé à Clang et GCC. Il n'y a aucun avertissement et la sortie est "???"

#include <stdio.h>

int main()
{
    int a[2]={ 10, 1 };

    while( a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ] )
        printf("?");

    return 0;
}
CustomCalc
la source
36
Certains d'entre eux sont des digraphes .
juanchopanza
12
@Kay, non dans ce cas::> =] puis un [...] >> = a [...]
Adriano Repetti
6
@Marc Je ne pense pas que cela puisse être ">>> =" car cela ne compilerait pas, mais le code ci-dessus compile en fait.
CustomCalc
21
Le 0x.1P1est un littéral hexadécimal avec un exposant. C'est 0x.1la partie numérique, ou 1/16 ici. Le nombre après le «P» est la puissance de deux, le nombre est multiplié par. 0x.1p1Est donc vraiment 1/16 * 2, ou 1/8. Et si vous vous posiez la question, 0xFULLc'est juste 0xF, et ULLc'est le suffixe pour ununsigned long long
jackarms
71
Syntaxe C - matériel sans fin pour les experts et les amateurs de futilités, mais finalement pas si important.
Kerrek SB

Réponses:

468

La ligne:

while( a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ] )

contient les digraphes :> et <:, qui se traduisent par ]et [respectivement, c'est donc équivalent à:

while( a[ 0xFULL?'\0':-1 ] >>= a[ !!0X.1P1 ] )

Le littéral 0xFULLest le même que 0xF(qui est hex pour 15); le ULLspécifie juste que c'est un unsigned long longlittéral . Dans tous les cas, en tant que booléen, c'est vrai, donc 0xFULL ? '\0' : -1évalue à '\0', qui est un littéral de caractère dont la valeur numérique est simplement 0.

Pendant ce temps, 0X.1P1est un littéral à virgule flottante hexadécimal égal à 2/16 = 0,125. Dans tous les cas, étant différent de zéro, c'est également vrai en tant que booléen, donc le nier deux fois avec !!produit à nouveau 1. Ainsi, le tout se résume à:

while( a[0] >>= a[1] )

L'opérateur >>=est une affectation composée qui décale son opérande gauche vers la droite du nombre de bits donné par l'opérande de droite et renvoie le résultat. Dans ce cas, l'opérande de droite a[1]a toujours la valeur 1, il est donc équivalent à:

while( a[0] >>= 1 )

ou équivalent:

while( a[0] /= 2 )

La valeur initiale de a[0]est 10. Après avoir décalé une fois vers la droite, il devient 5, puis (arrondi vers le bas) 2, puis 1 et enfin 0, point auquel la boucle se termine. Ainsi, le corps de la boucle est exécuté trois fois.

Ilmari Karonen
la source
18
Pourriez - vous s'il vous plaît donner des détails sur la Pdans 0X.1P1.
kay - SE is evil
77
@Kay: C'est la même chose que edans 10e5, sauf que vous devez utiliser pdes littéraux hexadécimaux car ec'est un chiffre hexadécimal.
Dietrich Epp
9
@Kay: Les littéraux hexadécimaux font partie de C99, mais GCC les accepte également en code C ++ . Comme le note Dietrich, le psépare la mantisse et l'exposant, tout comme la enotation flottante scientifique normale; une différence est que, avec des flotteurs hexadécimaux, la base de la partie exponentielle est 2 au lieu de 10, donc 0x0.1p1égale à 0x0.1 = 1/16 fois 2¹ = 2. (Dans tous les cas, rien de tout cela n'a d'importance ici; tout non différent de zéro la valeur fonctionnerait aussi bien là-bas.)
Ilmari Karonen
6
@chux: Apparemment, cela dépend si le code est compilé en C ou (comme il a été initialement étiqueté) C ++. Mais j'ai corrigé le texte pour dire "caractère littéral" au lieu de " charlittéral", et j'ai ajouté un lien Wikipedia. Merci!
Ilmari Karonen
8
Belle réduction.
Corey
69

Il s'agit d'un code assez obscur impliquant des digraphes , à savoir <:et :>qui sont des jetons alternatifs pour [et ]respectivement. Il y a aussi une certaine utilisation de l' opérateur conditionnel . Il y a aussi un opérateur de décalage de bits , l'affectation de décalage à droite >>=.

Ceci est une version plus lisible:

while( a[ 0xFULL ? '\0' : -1 ] >>= a[ !!0X.1P1 ] )

et une version encore plus lisible, remplaçant les expressions dans le []pour les valeurs qu'elles résolvent:

while( a[0] >>= a[1] )

Remplacer a[0]et a[1]pour leurs valeurs devrait permettre de comprendre facilement ce que fait la boucle, c'est-à-dire l'équivalent de:

int i = 10;
while( i >>= 1)

qui effectue simplement une division (entière) par 2 à chaque itération, produisant la séquence 5, 2, 1.

juanchopanza
la source
Je ne l'ai pas exécuté - cela ne produirait-il pas ????, cependant, plutôt que ???comme l'OP l'a obtenu? (Huh.) Codepad.org/nDkxGUNi fait des produits ???.
usr2564301
7
@Jongware le 10 a été divisé lors de la première itération. Ainsi, les valeurs évaluées par la boucle sont 5, 2, 1 et 0. Elle ne s'imprime donc que 3 fois.
MysticXG
42

Passons en revue l'expression de gauche à droite:

a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ]

La première chose que je remarque, c'est que nous utilisons l'opérateur ternaire depuis l'utilisation de ?. Donc, la sous-expression:

0xFULL ? '\0' : -1

dit "si 0xFULLest différent de zéro, retour '\0', sinon -1. 0xFULLest un littéral hexadécimal avec le suffixe long-long non signé - ce qui signifie que c'est un littéral hexadécimal de typeunsigned long long . Cela n'a pas vraiment d'importance, car 0xFpeut tenir dans un entier normal.

De plus, l'opérateur ternaire convertit les types des deuxième et troisième termes en leur type commun. '\0'est ensuite converti en int, qui est juste0 .

La valeur de 0xFest bien supérieure à zéro, elle passe donc. L'expression devient maintenant:

a[ 0 :>>>=a<:!!0X.1P1 ]

Ensuite, :>est un digraphe . C'est une construction qui se développe pour ]:

a[0 ]>>=a<:!!0X.1P1 ]

>>=est l'opérateur de décalage à droite signé, nous pouvons espacer cela apour le rendre plus clair.

De plus, <:est un digraphe qui se développe pour [:

a[0] >>= a[!!0X.1P1 ]

0X.1P1est un littéral hexadécimal avec un exposant. Mais peu importe la valeur, la valeur !!de tout ce qui n'est pas nul est vraie. 0X.1P1est 0.125qui est non nul, il devient donc:

a[0] >>= a[true]
-> a[0] >>= a[1]

Le >>=est l'opérateur de décalage vers la droite signé. Il modifie la valeur de son opérande gauche en décalant ses bits vers l'avant de la valeur sur le côté droit de l'opérateur. 10en binaire est 1010. Voici donc les étapes:

01010 >> 1 == 00101
00101 >> 1 == 00010
00010 >> 1 == 00001
00001 >> 1 == 00000

>>=renvoie le résultat de son fonctionnement, de sorte que tant que le décalage a[0]reste non nul à chaque fois que ses bits sont décalés de un vers la droite, la boucle continue. La quatrième tentative est où a[0]devient 0, donc la boucle n'est jamais entrée.

Par conséquent, ?est imprimé trois fois.

0x499602D2
la source
3
:>est un digraphe , pas un trigraphe. Il n'est pas géré par le préprocesseur, il est simplement reconnu comme un équivalent de jeton ].
Keith Thompson
@KeithThompson Thanks
0x499602D2
1
L'opérateur ternaire ( ?:) a un type qui est le type commun des deuxième et troisième termes. Le premier terme est toujours conditionnel et a un type bool. Puisque les deuxièmes et troisièmes termes ont le type intle résultat de l'opération ternaire sera int, non unsigned long long.
Corey
2
@KeithThompson, il pourrait être géré par le préprocesseur. Le préprocesseur doit connaître les digraphes car #et ##avoir des formes de digraphe; rien n'empêche une implémentation de traduire des digraphes en non-digraphes pendant les premières phases de la traduction
MM
@MattMcNabb Cela fait longtemps que je n'ai pas dû le savoir, mais l'IIRC en raison d'autres exigences, les digraphes doivent rester dans leur forme de digraphe jusqu'au moment où les jetons pp sont convertis en tokens (dès le début de la phase de traduction) 7).
zwol