Was ist der >>> = Operator in C?

294

Von einem Kollegen als Puzzle gegeben, kann ich nicht herausfinden, wie dieses C-Programm tatsächlich kompiliert und ausgeführt wird. Was ist dieser >>>=Operator und das seltsame 1P1Literal? Ich habe in Clang und GCC getestet. Es gibt keine Warnungen und die Ausgabe ist "???"

#include <stdio.h>

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

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

    return 0;
}
CustomCalc
quelle
36
Einige davon sind Digraphen .
Juanchopanza
12
@ Kay, nein in diesem Fall ::> =] dann ein [...] >> = ein [...]
Adriano Repetti
6
@Marc Ich glaube nicht, dass es ">>> =" sein kann, da dies nicht kompiliert werden würde, der obige Code jedoch tatsächlich kompiliert wird.
CustomCalc
21
Das 0x.1P1ist ein hexadezimales Literal mit einem Exponenten. Das 0x.1ist der Zahlenteil oder 1/16 hier. Die Zahl nach dem 'P' ist die Zweierpotenz, mit der die Zahl multipliziert wird. Also 0x.1p1ist wirklich 1/16 * 2 oder 1/8. Und wenn Sie sich fragen , über 0xFULLdas ist nur 0xF, und ULList das Suffix für eineunsigned long long
jackarms
71
C-Syntax - endloses Material für Experten und Trivia-Liebhaber, aber letztendlich nicht so wichtig.
Kerrek SB

Antworten:

468

Die Linie:

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

enthält die Digraphen :> und <:, die übersetzen , ]und die [jeweils so ist es äquivalent zu:

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

Das Literal 0xFULList dasselbe wie 0xF(was hexadezimal ist 15); Das gibt ULLnur an, dass es ein unsigned long longLiteral ist . In jedem Fall ist es als Boolescher Wert wahr, also wird 0xFULL ? '\0' : -1ausgewertet '\0', was ein Zeichenliteral ist, dessen numerischer Wert einfach ist 0.

Inzwischen 0X.1P1ist ein hexadezimales Gleitkomma-Literal gleich 2/16 = 0,125. Da es nicht Null ist, gilt es in jedem Fall auch als Boolescher Wert. Wenn Sie es also zweimal negieren, wird es !!erneut erzeugt 1. Somit vereinfacht sich das Ganze bis auf:

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

Der Operator >>=ist eine zusammengesetzte Zuweisung , die ihren linken Operanden um die vom rechten Operanden angegebene Anzahl von Bits nach rechts verschiebt und das Ergebnis zurückgibt. In diesem Fall hat der richtige Operand a[1]immer den Wert 1, also entspricht er:

while( a[0] >>= 1 )

oder gleichwertig:

while( a[0] /= 2 )

Der Anfangswert von a[0]ist 10. Nach einmaligem Verschieben nach rechts wird er zu 5, dann (abgerundet) zu 2, dann zu 1 und schließlich zu 0, an welchem ​​Punkt die Schleife endet. Somit wird der Schleifenkörper dreimal ausgeführt.

Ilmari Karonen
quelle
18
Könnten Sie bitte auf das erarbeiten Pin 0X.1P1.
Kay - SE ist böse
77
@ Kay: Es ist das gleiche wie ein 10e5, außer dass Sie pfür hexadezimale Literale verwenden müssen, da ees sich um eine hexadezimale Ziffer handelt.
Dietrich Epp
9
@ Kay: Die Hex-Float-Literale sind Teil von C99, aber GCC akzeptiert sie auch in C ++ - Code . Wie Dietrich bemerkt, ptrennt das die Mantisse und den Exponenten, genau wie ein der normalen wissenschaftlichen Float-Notation; Ein Unterschied besteht darin, dass bei Hex-Floats die Basis des Exponentialteils 2 statt 10 ist, also 0x0.1p10x0,1 = 1/16 mal 2¹ = 2. (In jedem Fall ist hier nichts davon von Bedeutung; keine Nicht-Null Wert würde dort gleich gut funktionieren.)
Ilmari Karonen
6
@chux: Anscheinend hängt das davon ab, ob der Code als C oder (wie ursprünglich markiert) C ++ kompiliert wurde. Aber ich habe den Text so korrigiert, dass er "Zeichenliteral" anstelle von " charLiteral" sagt , und einen Wikipedia-Link hinzugefügt. Vielen Dank!
Ilmari Karonen
8
Schöne Reduktion.
Corey
69

Es ist ein ziemlich dunkler Code, der Digraphen enthält , nämlich <:und :>die alternative Token für [bzw. ]sind. Es gibt auch eine Verwendung des bedingten Operators . Es gibt auch einen Bitverschiebungsoperator , die richtige Verschiebungszuordnung >>=.

Dies ist eine besser lesbare Version:

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

und eine noch besser lesbare Version, die die Ausdrücke in []den Werten ersetzt, in die sie aufgelöst werden:

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

Das Ersetzen a[0]und a[1]für ihre Werte sollte es einfach machen, herauszufinden, was die Schleife tut, dh das Äquivalent von:

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

Dies führt einfach eine (ganzzahlige) Division durch 2 in jeder Iteration durch, wodurch die Sequenz erzeugt wird 5, 2, 1.

Juanchopanza
quelle
Ich habe es nicht ausgeführt - würde dies jedoch nicht produzieren ????, anstatt ???wie es das OP bekam? (Huh.) Codepad.org/nDkxGUNi tut produzieren ???.
usr2564301
7
@Jongware Die 10 wurden bei der ersten Iteration geteilt. Die von der Schleife ausgewerteten Werte sind also 5, 2, 1 und 0. Sie wird also nur dreimal ausgedruckt.
MysticXG
42

Lassen Sie uns den Ausdruck von links nach rechts durchgehen:

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

Das erste, was mir auffällt, ist, dass wir den ternären Operator aus der Verwendung von verwenden ?. Also der Unterausdruck:

0xFULL ? '\0' : -1

sagt: "Wenn 0xFULLnicht Null ist, geben Sie '\0'andernfalls zurück -1. 0xFULList ein hexadezimales Literal mit dem vorzeichenlosen Long-Long-Suffix - was bedeutet, dass es ein hexadezimales Literal vom Typ ist unsigned long long. Das ist jedoch nicht wirklich wichtig, da 0xFes in eine reguläre Ganzzahl passen kann.

Außerdem konvertiert der ternäre Operator die Typen des zweiten und dritten Terms in ihren gemeinsamen Typ. '\0'wird dann konvertiert int, was gerecht ist 0.

Der Wert von 0xFist viel größer als Null, also geht er vorbei. Der Ausdruck wird jetzt:

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

Als nächstes :>ist ein Digraph . Es ist ein Konstrukt, das sich erweitert zu ]:

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

>>=ist der signierte Rechtsschichtbetreiber, wir können das ausräumen a, um es klarer zu machen.

Darüber hinaus <:ist ein Digraph, der sich erweitert zu [:

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

0X.1P1ist ein hexadezimales Literal mit einem Exponenten. Aber egal welcher Wert, !!alles, was nicht Null ist, ist wahr. 0X.1P1ist 0.125was nicht Null ist, also wird es:

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

Das >>=ist der signierte Rechtsschichtbetreiber. Es ändert den Wert seines linken Operanden, indem es seine Bits um den Wert auf der rechten Seite des Operators nach vorne verschiebt. 10in binär ist 1010. Also hier sind die Schritte:

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

>>=Gibt das Ergebnis seiner Operation zurück. Solange die Verschiebung a[0]für jedes Mal, wenn ihre Bits um eins nach rechts verschoben werden, ungleich Null bleibt, wird die Schleife fortgesetzt. Der vierte Versuch ist, wo a[0]wird 0, so dass die Schleife nie betreten wird.

Infolgedessen ?wird dreimal gedruckt.

0x499602D2
quelle
3
:>ist ein Digraph , kein Trigraph. Es wird nicht vom Präprozessor verarbeitet, sondern lediglich als Token-Äquivalent zu erkannt ].
Keith Thompson
@ KeithThompson Danke
0x499602D2
1
Der ternäre Operator ( ?:) hat einen Typ, der dem allgemeinen Typ des zweiten und dritten Terms entspricht. Der erste Term ist immer eine Bedingung und hat einen Typ bool. Da sowohl der zweite als auch der dritte Term vom Typ sind, wird intdas Ergebnis der ternären Operation intnicht sein unsigned long long.
Corey
2
@KeithThompson könnte vom Präprozessor verarbeitet werden. Der Präprozessor hat über Digraphe weil wissen #und ##haben digraph Formen; Nichts hindert eine Implementierung daran, Digraphen in den frühen Übersetzungsphasen in Nicht-Digraphen zu übersetzen
MM
@MattMcNabb Es ist lange her, dass ich das wissen musste, aber IIRC muss aufgrund anderer Anforderungen in ihrer Digraphenform bleiben, bis pp-Token in Token umgewandelt werden (gleich zu Beginn der Übersetzungsphase) 7).
zwol