Cos'è l'operatore >>> = in C?


294

Dato da un collega come un enigma, non riesco a capire come questo programma C compili e funzioni. Cos'è questo >>>=operatore e lo strano 1P1letterale? Ho provato a Clang e GCC. Non ci sono avvisi e l'output è "???"

#include <stdio.h>

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

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

    return 0;
}

36
Alcuni di questi sono digrafi .
juanchopanza,

12
@Kay, no in questo caso::> =] quindi a [...] >> = a [...]
Adriano Repetti

6
@Marc Non credo che possa essere ">>> =" perché non si sarebbe compilato, tuttavia il codice sopra si compila effettivamente.
Calcolo personalizzato

21
Il 0x.1P1è un letterale esadecimale con un esponente. La 0x.1è il numero di parte, o 1/16 qui. Il numero dopo la 'P' è la potenza di due per cui viene moltiplicato il numero. Quindi 0x.1p1è davvero 1/16 * 2 o 1/8. E se ti stavi chiedendo 0xFULLche è giusto 0xF, ed ULLè il suffisso per ununsigned long long
sciami

71
Sintassi C: materiale infinito per esperti e curiosi, ma alla fine non è poi così importante.
Kerrek SB,

Risposte:


468

La linea:

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

contiene i digrafi :> e <:, che si traducono in ]e [rispettivamente, quindi equivalgono a:

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

Il letterale 0xFULLè lo stesso di 0xF(che è hex per 15); il ULLgiusto specifica che è unsigned long longletterale . In ogni caso, come booleano è vero, quindi 0xFULL ? '\0' : -1valuta '\0', che è un carattere letterale il cui valore numerico è semplicemente 0.

Nel frattempo, 0X.1P1è un virgola mobile esadecimale letterale pari a 2/16 = 0,125. In ogni caso, essendo diverso da zero, è anche vero come un valore booleano, quindi negarlo due volte con di !!nuovo produce 1. Pertanto, il tutto si semplifica fino a:

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

L'operatore >>=è un incarico composto che sposta i bit dell'operando di sinistra a destra in base al numero di bit dato dall'operando di destra e restituisce il risultato. In questo caso, l'operando giusto a[1]ha sempre il valore 1, quindi equivale a:

while( a[0] >>= 1 )

o, equivalentemente:

while( a[0] /= 2 )

Il valore iniziale di a[0]è 10. Dopo aver spostato a destra una volta, diventa 5, quindi (arrotondando per difetto) 2, quindi 1 e infine 0, a quel punto il ciclo termina. Pertanto, il corpo del loop viene eseguito tre volte.


18
Potresti per favore approfondire l' Pin 0X.1P1.
kay - SE è malvagio il

77
@Kay: è lo stesso di ein 10e5, tranne che devi usare i pletterali esadecimali perché eè una cifra esadecimale.
Dietrich Epp,

9
@Kay: i letterali float esadecimali fanno parte di C99, ma GCC li accetta anche in codice C ++ . Come osserva Dietrich, psepara la mantissa e l'esponente, proprio come enella normale notazione float scientifica; una differenza è che, con float esadecimali, la base della parte esponenziale è 2 invece di 10, quindi è 0x0.1p1uguale a 0x0,1 = 1/16 volte 2¹ = 2. (In ogni caso, nulla di ciò che conta qui; qualsiasi diverso da zero valore funzionerebbe altrettanto bene lì.)
Ilmari Karonen

6
@chux: Apparentemente, dipende dal fatto che il codice sia compilato come C o (come era originariamente taggato) C ++. Ma ho corretto il testo per dire "carattere letterale" anziché " charletterale" e ho aggiunto un link Wikipedia. Grazie!
Ilmari Karonen,

8
Bella riduzione.
Corey,

69

È un codice piuttosto oscuro che coinvolge i digrafi , vale a dire <:e :>che sono token alternativi per [e ]rispettivamente. C'è anche un certo uso dell'operatore condizionale . C'è anche un operatore di spostamento dei bit , l'assegnazione del cambio corretta >>=.

Questa è una versione più leggibile:

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

e una versione ancora più leggibile, sostituendo le espressioni in []per i valori che risolvono per:

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

Sostituendo a[0]e a[1]per i loro valori dovrebbe essere facile capire cosa sta facendo il loop, cioè l'equivalente di:

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

che sta semplicemente eseguendo una divisione (intera) per 2 in ogni iterazione, producendo la sequenza 5, 2, 1.


Non l'ho eseguito - questo non avrebbe prodotto ????, tuttavia, piuttosto che ???come l'OP ha ottenuto? (Huh.) Codepad.org/nDkxGUNi fa prodotti ???.
usr2564301

7
@Jongware il 10 è stato diviso alla prima iterazione. Quindi i valori valutati dal loop sono 5, 2, 1 e 0. Quindi viene stampato solo 3 volte.
MysticXG,

42

Esaminiamo l'espressione da sinistra a destra:

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

La prima cosa che noto è che stiamo usando l'operatore ternario dall'uso di ?. Quindi la sottoespressione:

0xFULL ? '\0' : -1

sta dicendo "se 0xFULLè diverso da zero, return '\0', altrimenti -1. 0xFULLè un letterale esadecimale con il suffisso long-long senza segno - il che significa che è un letterale esadecimale di tipo unsigned long long. Ciò non ha molta importanza, perché 0xFpuò rientrare in un intero normale.

Inoltre, l'operatore ternario converte i tipi del secondo e terzo termine nel loro tipo comune. '\0'viene quindi convertito in int, che è giusto 0.

Il valore di 0xFè molto più grande di zero, quindi passa. L'espressione ora diventa:

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

Successivamente, :>è un digrafo . È un costrutto che si espande per ]:

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

>>=è l'operatore di spostamento a destra firmato, possiamo distanziarlo aper renderlo più chiaro.

Inoltre, <:è un digrafo che si espande a [:

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

0X.1P1è un letterale esadecimale con un esponente. Ma non importa il valore, il valore !!di qualsiasi cosa diversa da zero è vero. 0X.1P1è 0.125che è diverso da zero, quindi diventa:

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

Il >>=è l'operatore spostamento a destra firmato. Cambia il valore dell'operando di sinistra spostando i suoi bit in avanti del valore sul lato destro dell'operatore. 10in binario è 1010. Quindi, ecco i passaggi:

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

>>=restituisce il risultato del suo funzionamento, quindi finché lo spostamento a[0]rimane diverso da zero per ogni volta che i suoi bit vengono spostati a destra di uno, il loop continuerà. Il quarto tentativo è dove a[0]diventa 0, quindi il ciclo non viene mai inserito.

Di conseguenza, ?viene stampato tre volte.


3
:>è un digrafo , non una trigrafia. Non è gestito dal preprocessore, è semplicemente riconosciuto come token equivalente a ].
Keith Thompson,

@KeithThompson Thanks
0x499602D2

1
L'operatore ternario ( ?:) ha un tipo che è il tipo comune di secondo e terzo termine. Il primo termine è sempre un condizionale e ha un tipo bool. Poiché sia ​​il secondo che il terzo termine hanno tipo, intil risultato dell'operazione ternaria sarà int, no unsigned long long.
Corey,

2
@KeithThompson potrebbe essere gestito dal preprocessore. Il preprocessore deve conoscere i digrafi perché #e ##avere forme di digrafo; non c'è nulla che possa impedire un'implementazione dalla traduzione di digrafi a non-digrafi durante le prime fasi della traduzione
MM

@MattMcNabb È passato molto tempo da quando ho dovuto saperlo, ma IIRC come conseguenza di altri requisiti, i digraph devono rimanere nella loro forma di digraph fino al punto in cui i token pp vengono convertiti in token (proprio all'inizio della fase di traduzione 7).
zwol,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.