Apa yang dimaksud dengan operator >>> = dalam C?

294

Diberikan oleh seorang rekan sebagai teka-teki, saya tidak tahu bagaimana sebenarnya program C ini mengkompilasi dan berjalan. Apa >>>=operator ini dan 1P1literal aneh ? Saya telah menguji di dentang dan GCC. Tidak ada peringatan dan hasilnya "???"

#include <stdio.h>

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

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

    return 0;
}
CustomCalc
sumber
36
Beberapa di antaranya adalah digraf .
juanchopanza
12
@Kay, tidak dalam hal ini::> =] lalu [...] >> = a [...]
Adriano Repetti
6
@Marc Saya tidak berpikir itu bisa ">>> =" karena itu tidak dapat dikompilasi, namun kode di atas sebenarnya mengkompilasi.
CustomCalc
21
Ini 0x.1P1adalah heksadesimal literal dengan eksponen. Ini 0x.1adalah nomor bagian, atau 1/16 di sini. Angka setelah 'P' adalah kekuatan dua angka dikalikan dengan. Begitu 0x.1p1juga 1/16 * 2, atau 1/8. Dan jika Anda bertanya-tanya tentang 0xFULLitu saja 0xF, dan ULLmerupakan akhiran untukunsigned long long
jackarms
71
C sintaks - bahan tak berujung untuk para pakar dan trivia, tetapi pada akhirnya tidak terlalu penting.
Kerrek SB

Jawaban:

468

Garis:

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

berisi digraf :> dan <:, yang diterjemahkan ke ]dan [masing - masing, sehingga setara dengan:

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

Secara literal 0xFULLsama dengan 0xF(yang merupakan hex untuk 15); yang ULLhanya menetapkan bahwa itu adalah unsigned long longliteral . Bagaimanapun, sebagai boolean itu benar, maka 0xFULL ? '\0' : -1dievaluasi menjadi '\0', yang merupakan karakter literal yang nilai numeriknya sederhana 0.

Sementara itu, 0X.1P1adalah floating point heksadesimal literal sama untuk 2/16 = 0,125. Bagaimanapun, menjadi non-nol, itu juga benar sebagai boolean, jadi meniadakannya dua kali dengan !!menghasilkan lagi 1. Dengan demikian, semuanya disederhanakan menjadi:

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

Operator >>=adalah penugasan majemuk yang menggeser operan kirinya ke kanan dengan jumlah bit yang diberikan oleh operan kanan, dan mengembalikan hasilnya. Dalam hal ini, operan yang tepat a[1]selalu memiliki nilai 1, sehingga setara dengan:

while( a[0] >>= 1 )

atau, yang setara:

while( a[0] /= 2 )

Nilai awal a[0]adalah 10. Setelah bergeser ke kanan sekali, itu menjadi 5, lalu (pembulatan ke bawah) 2, lalu 1 dan akhirnya 0, di mana titik loop berakhir. Jadi, loop body dieksekusi tiga kali.

Ilmari Karonen
sumber
18
Bisakah Anda menguraikan di Pdalam 0X.1P1.
kay - SE jahat
77
@Kay: Sama seperti edalam 10e5, kecuali Anda harus menggunakan puntuk heksadesimal literal karena emerupakan digit heksadesimal.
Dietrich Epp
9
@Kay: Hex float literals adalah bagian dari C99, tetapi GCC juga menerimanya dalam kode C ++ . Seperti yang dicatat Dietrich, pmemisahkan mantissa dan eksponen, seperti halnya enotasi float ilmiah normal; satu perbedaan adalah bahwa, dengan hex floats, basis dari bagian eksponensial adalah 2 bukannya 10, jadi 0x0.1p1sama dengan 0x0.1 = 1/16 kali 2¹ = 2. (Dalam hal apa pun, tidak ada yang penting di sini; tidak nol nilai akan bekerja sama baiknya di sana.)
Ilmari Karonen
6
@ chux: Rupanya, itu tergantung pada apakah kode dikompilasi sebagai C atau (seperti yang awalnya ditandai) C ++. Tapi saya memperbaiki teks untuk mengatakan "karakter literal", bukan " charliteral", dan menambahkan tautan Wikipedia. Terima kasih!
Ilmari Karonen
8
Pengurangan bagus.
Corey
69

Ini adalah beberapa kode yang agak kabur yang melibatkan digraf , yaitu <:dan :>yang merupakan token alternatif untuk [dan ]masing - masing. Ada juga beberapa penggunaan operator kondisional . Ada juga operator yang sedikit bergeser , penugasan shift yang tepat >>=.

Ini adalah versi yang lebih mudah dibaca:

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

dan versi yang lebih mudah dibaca, menggantikan ekspresi dalam []untuk nilai-nilai yang mereka tetapkan untuk:

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

Mengganti a[0]dan a[1]untuk nilainya harus membuatnya mudah untuk mengetahui apa yang dilakukan loop, yaitu setara dengan:

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

yang hanya melakukan pembagian (integer) oleh 2 di setiap iterasi, menghasilkan urutan 5, 2, 1.

juanchopanza
sumber
Saya tidak menjalankannya - akankah ini tidak menghasilkan ????, alih-alih ???seperti yang didapat OP? (Huh.) Codepad.org/nDkxGUNi memang menghasilkan ???.
usr2564301
7
@ Jongware 10 terbagi pada iterasi pertama. Jadi nilai yang dievaluasi oleh loop adalah 5, 2, 1, dan 0. Jadi hanya mencetak 3 kali.
MysticXG
42

Mari kita lihat ungkapan dari kiri ke kanan:

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

Hal pertama yang saya perhatikan adalah bahwa kami menggunakan operator ternary dari penggunaan ?. Jadi subekspresi:

0xFULL ? '\0' : -1

mengatakan "jika 0xFULLbukan nol, kembali '\0', jika tidak -1. 0xFULLadalah heksadesimal heksadesimal dengan akhiran panjang-panjang unsigned - yang berarti itu adalah heksadesimal harafiah jenis unsigned long long. Itu tidak terlalu penting, karena 0xFdapat masuk ke dalam bilangan bulat biasa.

Juga, operator ternary mengubah jenis istilah kedua dan ketiga menjadi jenis umum mereka. '\0'kemudian dikonversi menjadi int, yang adil 0.

Nilai 0xFjauh lebih besar dari nol, sehingga melewati. Ekspresi sekarang menjadi:

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

Selanjutnya, :>adalah digraf . Ini adalah konstruksi yang diperluas ke ]:

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

>>=adalah operator shift kanan yang telah ditandatangani, kita dapat mengaturnya aagar lebih jelas.

Selain itu, <:adalah digraf yang diperluas ke [:

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

0X.1P1adalah heksadesimal literal dengan eksponen. Tapi tidak peduli nilainya, !!apa pun yang bukan nol itu benar. 0X.1P1adalah 0.125yang bukan nol, sehingga menjadi:

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

The >>=merupakan operator pergeseran kanan ditandatangani. Ini mengubah nilai operan kirinya dengan menggeser bitnya ke depan dengan nilai di sisi kanan operator. 10dalam biner adalah 1010. Jadi, inilah langkah-langkahnya:

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

>>=mengembalikan hasil operasinya, sehingga selama pergeseran a[0]tetap tidak nol untuk setiap kali bitnya bergeser tepat satu, loop akan terus berlanjut. Upaya keempat adalah di mana a[0]menjadi 0, sehingga loop tidak pernah dimasukkan.

Hasilnya, ?dicetak tiga kali.

0x499602D2
sumber
3
:>adalah digraf , bukan trigraph. Ini tidak ditangani oleh preprocessor, itu hanya diakui sebagai token yang setara ].
Keith Thompson
@KeithThompson Terima kasih
0x499602D2
1
Operator ternary ( ?:) memiliki tipe yang merupakan tipe umum dari suku kedua dan ketiga. Istilah pertama selalu bersyarat dan memiliki tipe bool. Karena kedua istilah ketiga dan ketiga telah mengetik inthasil operasi ternary akan int, tidak unsigned long long.
Corey
2
@KeithThompson itu bisa ditangani oleh preprocessor. Praproses harus mengetahui tentang digraf karena #dan ##memiliki bentuk digraf; tidak ada yang menghentikan implementasi dari menerjemahkan digraf ke non-digraf selama fase terjemahan awal
MM
@MattMcNabb Sudah lama sejak saya harus mengetahui hal ini, tetapi IIRC sebagai konsekuensi dari persyaratan lain, digraf harus tetap dalam bentuk digraf mereka sampai titik di mana pp-token dikonversi menjadi token (tepat di awal fase terjemahan 7).
zwol