O que é o operador >>> = em C?

294

Dado por um colega como um quebra-cabeça, não consigo descobrir como esse programa C realmente compila e executa. O que é esse >>>=operador e o 1P1literal estranho ? Eu testei em Clang e GCC. Não há avisos e a saída é "???"

#include <stdio.h>

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

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

    return 0;
}
CustomCalc
fonte
36
Alguns são digrafos .
Juanchopanza
12
@Kay, não neste caso::> =] então um [...] >> = um [...]
Adriano Repetti
6
@ Marc eu não acho que pode ser ">>> =" porque isso não seria compilado, no entanto, o código acima realmente compila.
precisa
21
O 0x.1P1é um literal hexadecimal com um expoente. A 0x.1é a parte do número, ou 1/16 aqui. O número após o 'P' é a potência de dois, multiplicado pelo número. Então 0x.1p1é realmente 1/16 * 2 ou 1/8. E se você estava pensando sobre 0xFULLisso é apenas 0xF, e ULLé o sufixo para umunsigned long long
jackarms
71
Sintaxe C - material interminável para especialistas e amantes de curiosidades, mas no final não é tão importante.
27514 Kerrek SB

Respostas:

468

A linha:

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

contém os digrafos :> e <:, que se traduzem para ]e [respectivamente, portanto, é equivalente a:

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

O literal 0xFULLé o mesmo que 0xF(que é hexadecimal para 15); o ULLjust especifica que é um unsigned long longliteral . De qualquer forma, como booleano é verdade, é 0xFULL ? '\0' : -1avaliado como '\0', que é um literal de caractere cujo valor numérico é simplesmente 0.

Enquanto isso, 0X.1P1é um literal de ponto flutuante hexadecimal igual a 2/16 = 0,125. De qualquer forma, sendo diferente de zero, também é verdadeiro como um booleano, portanto, negá-lo duas vezes com !!novamente produz 1. Assim, tudo simplifica:

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

O operador >>=é uma atribuição composta que desloca o operando esquerdo para a direita pelo número de bits fornecido pelo operando direito e retorna o resultado. Nesse caso, o operando certo a[1]sempre tem o valor 1, portanto é equivalente a:

while( a[0] >>= 1 )

ou equivalente:

while( a[0] /= 2 )

O valor inicial de a[0]é 10. Depois de mudar para a direita uma vez, torna-se 5, depois (arredondando para baixo) 2, depois 1 e finalmente 0, altura em que o loop termina. Assim, o corpo do loop é executado três vezes.

Ilmari Karonen
fonte
18
Você poderia por favor elaborar sobre o Pno 0X.1P1.
kay - SE is evil
77
@ Kay: É o mesmo que eem 10e5, exceto que você tem que usar ppara literais hexadecimais, porque eé um dígito hexadecimal.
Dietrich Epp
9
@Kay: Os literais de flutuador hexadecimal fazem parte do C99, mas o GCC também os aceita no código C ++ . Como observa Dietrich, o psepara a mantissa e o expoente, exatamente como ena notação científica normal; uma diferença é que, com flutuadores hexadecimais, a base da parte exponencial é 2 em vez de 10, então 0x0.1p1é igual a 0x0,1 = 1/16 vezes 2¹ = 2. (De qualquer forma, nada disso importa aqui; qualquer valor diferente de zero valor funcionaria igualmente bem lá).
Ilmari Karonen
6
@chux: Aparentemente, isso depende se o código é compilado como C ou (como foi originalmente marcado) C ++. Mas corrigi o texto para dizer "literal de caracteres" em vez de " charliteral" e adicionei um link da Wikipedia. Obrigado!
Ilmari Karonen
8
Boa redução.
Corey
69

É um código bastante obscuro que envolve dígrafos , a saber, <:e :>que são tokens alternativos para [e ]respectivamente. Também há algum uso do operador condicional . Há também um operador de troca de marchas, a atribuição de troca certa >>=.

Esta é uma versão mais legível:

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

e uma versão ainda mais legível, substituindo as expressões no []pelos valores que eles resolvem:

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

A substituição a[0]e a[1]por seus valores deve facilitar a compreensão do que o loop está fazendo, ou seja, o equivalente a:

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

que está simplesmente executando a divisão (inteira) por 2 em cada iteração, produzindo a sequência 5, 2, 1.

juanchopanza
fonte
Eu não o executei - isso não produziria ????, e não ???como o OP conseguiu? (Hum.) Codepad.org/nDkxGUNi faz produto ???.
precisa saber é o seguinte
7
@Jongware the 10 ficou dividido na primeira iteração. Portanto, os valores que estão sendo avaliados pelo loop são 5, 2, 1 e 0. Portanto, ele é impresso apenas 3 vezes.
MysticXG
42

Vamos passar pela expressão da esquerda para a direita:

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

A primeira coisa que noto é que estamos usando o operador ternário do uso de ? . Então a subexpressão:

0xFULL ? '\0' : -1

está dizendo "se 0xFULLfor diferente de zero, retorne '\0', caso contrário -1. 0xFULLé um literal hexadecimal com o sufixo longo não assinado - o que significa que é um literal hexadecimal do tipo unsigned long long. Isso realmente não importa, porque 0xFpode caber dentro de um número inteiro regular.

Além disso, o operador ternário converte os tipos do segundo e do terceiro termos no tipo comum. '\0'é então convertido para int, o que é justo 0.

O valor de 0xFé muito maior que zero, então passa. A expressão agora se torna:

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

Em seguida, :>é um dígrafo . É uma construção que se expande para ]:

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

>>=é o operador de turno certo assinado, podemos separá-lo apara torná-lo mais claro.

Além disso, <:é um dígrafo que se expande para [:

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

0X.1P1é um literal hexadecimal com um expoente. Mas não importa o valor, o valor !!de qualquer coisa que não seja zero é verdadeiro. 0X.1P1é 0.125que é diferente de zero, então ele se torna:

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

O >>=é o operador de turno certo assinado. Ele altera o valor do seu operando esquerdo, deslocando seus bits para a frente pelo valor no lado direito do operador. 10em binário é 1010. Então, aqui estão as etapas:

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

>>=retorna o resultado de sua operação, desde que o deslocamento a[0]permaneça diferente de zero para cada vez que seus bits forem deslocados para a direita em um, o loop continuará. A quarta tentativa é ondea[0] fica 0, então o loop nunca é inserido.

Como resultado, ?é impresso três vezes.

0x499602D2
fonte
3
:>é um dígrafo , não um trigrafo. Não é manipulado pelo pré-processador, é simplesmente reconhecido como um token equivalente a ].
Keith Thompson
@KeithThompson Obrigado
0x499602D2
1
O operador ternário ( ?:) tem um tipo que é o tipo comum do segundo e terceiro termos. O primeiro termo é sempre uma condição e tem um tipo bool. Como o segundo e o terceiro termos têm o tipo, into resultado da operação ternária será int, não unsigned long long.
Corey
2
@KeithThompson, ele poderia ser tratado pelo pré-processador. O pré-processador tem de saber sobre dígrafos porque #e ##tem dígrafo formas; não há nada que uma implementação de traduzir dígrafos a não dígrafos durante as fases de tradução iniciais
MM
@MattMcNabb Já faz muito tempo que eu sabia disso, mas como o IIRC, como consequência de outros requisitos, os digrafos precisam permanecer em seu formato digraph até o ponto em que os tokens pp são convertidos em tokens (logo no início da fase de tradução 7)
precisa saber é