Co to jest operator >>> = w C?

294

Biorąc pod uwagę kolegę jako zagadkę, nie mogę zrozumieć, jak ten program C faktycznie się kompiluje i działa. Co to jest ten >>>=operator i dziwny 1P1literał? Testowałem w Clang i GCC. Nie ma ostrzeżeń, a wynikiem jest „???”

#include <stdio.h>

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

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

    return 0;
}
CustomCalc
źródło
36
Niektóre z nich to digrafy .
juanchopanza
12
@Kay, nie w tym przypadku::> =] następnie [...] >> = a [...]
Adriano Repetti
6
@Marc Nie sądzę, że może to być „>>> =”, ponieważ to się nie skompiluje, jednak powyższy kod faktycznie się kompiluje.
CustomCalc,
21
Jest 0x.1P1to literał szesnastkowy z wykładnikiem wykładniczym. Jest 0x.1to część liczbowa, czyli 1/16 tutaj. Liczba po „P” jest potęgą dwóch, przez którą liczba jest mnożona. Tak 0x.1p1naprawdę jest 1/16 * 2 lub 1/8. A jeśli zastanawiasz się nad 0xFULLtym, to jest po prostu 0xFi ULLjest to przyrostek dlaunsigned long long
jackarms
71
Składnia C - niekończący się materiał dla miłośników ciekawostek i ciekawostek, ale ostatecznie nie tak ważny.
Kerrek SB,

Odpowiedzi:

468

Linia:

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

zawiera digrafy :> i <:, które tłumaczą odpowiednio na ]i [, więc jest to równoważne z:

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

Dosłowność 0xFULLjest taka sama jak 0xF(dla której jest hex 15); po ULLprostu określa, że jest to unsigned long longdosłowne . W każdym razie, jako wartość logiczna jest to prawda, więc 0xFULL ? '\0' : -1ocenia na '\0', czyli dosłowny znak, którego wartość liczbowa jest po prostu 0.

Tymczasem 0X.1P1jest szesnastkowym literałem zmiennoprzecinkowym równym 2/16 = 0,125. W każdym razie, ponieważ jest niezerowa, jest również prawdziwa jako wartość logiczna, więc podwójna negacja !!powoduje ponownie 1. W ten sposób całość upraszcza się do:

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

Operator >>=jest złożonym przypisaniem, które przesuwa bit w lewo swojego operandu w prawo o liczbę bitów podaną przez prawy operand i zwraca wynik. W takim przypadku właściwy operand a[1]ma zawsze wartość 1, więc jest równoważny z:

while( a[0] >>= 1 )

lub równoważnie:

while( a[0] /= 2 )

Początkowa wartość a[0]wynosi 10. Po jednokrotnym przesunięciu w prawo staje się 5, następnie (zaokrąglając w dół) 2, następnie 1 i na końcu 0, w którym momencie pętla się kończy. W ten sposób ciało pętli zostanie wykonane trzy razy.

Ilmari Karonen
źródło
18
Czy mógłbyś rozwinąć na PIN 0X.1P1.
kay - SE is evil
77
@Kay: Jest taki sam jak ew 10e5, z tym wyjątkiem, że musisz używać pliterałów szesnastkowych, ponieważ ejest to cyfra szesnastkowa.
Dietrich Epp,
9
@Kay: Szesnastkowe literały zmiennoprzecinkowe są częścią C99, ale GCC akceptuje je również w kodzie C ++ . Jak zauważa Dietrich, poddziela mantysę i wykładnik, tak jak ew normalnym naukowym zapisie zmiennoprzecinkowym; jedna różnica polega na tym, że przy 0x0.1p1liczbach zmiennoprzecinkowych podstawa części wykładniczej wynosi 2 zamiast 10, więc równa się 0x0.1 = 1/16 razy 2¹ = 2. (W każdym razie nic z tego nie ma znaczenia; żadne niezerowe wartość działałaby tam równie dobrze.)
Ilmari Karonen
6
@chux: Najwyraźniej zależy to od tego, czy kod jest skompilowany jako C czy (jak pierwotnie oznaczono) C ++. Ale poprawiłem tekst na „dosłowny znak” zamiast „ chardosłowny” i dodałem link do Wikipedii. Dzięki!
Ilmari Karonen,
8
Niezła redukcja.
Corey
69

Jest raczej pewne niejasne kod udziałem digrafy , mianowicie <:a :>które są alternatywne dla tokeny [i ]odpowiednio. Istnieje również pewne zastosowanie operatora warunkowego . Istnieje również nieco zmieniony operator , właściwe przypisanie zmiany >>=.

To jest bardziej czytelna wersja:

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

i jeszcze bardziej czytelną wersję, zastępując wyrażenia w []wartościach, które postanawiają:

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

Wymiana a[0]i a[1]dla ich wartości powinny ułatwić dowiedzieć się, co robi pętlę, czyli równowartość:

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

który po prostu wykonuje dzielenie (całkowite) przez 2 w każdej iteracji, tworząc sekwencję 5, 2, 1.

juanchopanza
źródło
Nie uruchomiłem go - czy to jednak nie przyniosłoby ????rezultatu, ???jak PO? (Huh.) Codepad.org/nDkxGUNi nie produkuje ???.
usr2564301,
7
@Jongware 10 zostało podzielone przy pierwszej iteracji. Tak więc wartości oceniane przez pętlę to 5, 2, 1 i 0. Więc drukuje się tylko 3 razy.
MysticXG,
42

Przejdźmy do wyrażenia od lewej do prawej:

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

Pierwszą rzeczą, którą zauważam, jest to, że korzystamy z operatora trójskładnikowego z użycia ?. Podwyrażenie:

0xFULL ? '\0' : -1

mówi „jeśli 0xFULLjest niezerowe, zwróć '\0', w przeciwnym razie -1. 0xFULLjest literą szesnastkową z niepodpisanym długim sufiksem - co oznacza, że ​​jest literą szesnastkową typu unsigned long long. To nie ma znaczenia, ponieważ 0xFmoże mieścić się w regularnej liczbie całkowitej.

Ponadto operator trójskładnikowy przekształca typy drugiego i trzeciego wyrażenia na ich wspólny typ. '\0'jest następnie konwertowany na int, co jest sprawiedliwe 0.

Wartość 0xFjest znacznie większa od zera, więc mija. Wyrażenie staje się teraz:

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

Dalej :>jest digraf . Jest to konstrukcja, która rozwija się do ]:

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

>>=jest podpisanym operatorem prawej zmiany, możemy ato zrobić, aby było wyraźniej.

Co więcej, <:jest to digraf, który rozwija się do [:

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

0X.1P1jest literałem szesnastkowym z wykładnikiem wykładniczym. Ale bez względu na wartość, !!wszystko, co nie jest zerem, jest prawdą. 0X.1P1jest 0.125niezerowe, więc staje się:

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

Jest >>=to podpisany operator prawej zmiany. Zmienia wartość lewego operandu, przesuwając bity do przodu o wartość po prawej stronie operatora. 10w formacie binarnym jest 1010. Oto kroki:

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

>>=zwraca wynik działania, więc dopóki przesunięcie a[0]pozostaje niezerowe za każdym razem, gdy jego bity są przesunięte o jeden w prawo, pętla będzie kontynuowana. Czwarta próba dotyczy miejsca, w którym a[0]się znajduje 0, więc pętla nigdy nie jest wprowadzana.

W rezultacie ?jest drukowany trzy razy.

0x499602D2
źródło
3
:>to digraf , a nie trigraf. Nie jest obsługiwany przez preprocesor, jest po prostu rozpoznawany jako token równoważny ].
Keith Thompson,
@KeithThompson Thanks
0x499602D2,
1
Operator trójskładnikowy ( ?:) ma typ, który jest typowym typem drugiego i trzeciego wyrażenia. Pierwszy termin jest zawsze warunkowy i ma swój typ bool. Ponieważ zarówno drugi i trzeci mają terminy wpisz intwynik potrójnego działania będzie int, nie unsigned long long.
Corey
2
@KeithThompson może być obsługiwany przez preprocesora. Preprocesor musi wiedzieć o digrafach bo #i ##mieć digraf formy; nic nie powstrzymuje implementacji od tłumaczenia digraphów na non-digraphs we wczesnych fazach tłumaczenia
MM
@MattMcNabb Minęło dużo czasu, odkąd musiałem to wiedzieć, ale IIRC z powodu innych wymagań, digrafy muszą pozostać w formie digrafów, aż do momentu, w którym tokeny pp zostaną zamienione na tokeny (na początku fazy tłumaczenia 7).
zwolnić