Cの>>> =演算子とは何ですか?


294

同僚からパズルとして与えられたので、このCプログラムが実際にどのようにコンパイルされて実行されるのか理解できません。この>>>=演算子と奇妙な1P1リテラルは何ですか?ClangとGCCでテストしました。警告はなく、出力は "???"です。

#include <stdio.h>

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

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

    return 0;
}

36
それらのいくつかはダイグラフです。
juanchopanza 2014

12
@ケイ、この場合は不可::> =]その後a [...] >> = a [...]
Adriano Repetti 2014

6
@Marc ">>> ="はコンパイルできないため、私はそれが ">>> ="になることはないと思いますが、上記のコードは実際にコンパイルされます。
CustomCalc 2014

21
0x.1P1指数と進リテラルです。0x.1番号の一部、またはここに1/16です。「P」の後の数は、2の累乗であり、数に乗算されます。つまり0x.1p1、実際には1/16 * 2、つまり1/8です。そして、あなたが疑問を抱いた場合には0xFULLそのことだけだ0xF、とULLのための接尾辞であるunsigned long long
jackarms

71
C構文-専門家や雑学クイズ愛好家のための無限の資料ですが、最終的にはそれほど重要ではありません。
Kerrek SB、2014

回答:


468

この線:

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

とにそれぞれ変換されるダイグラフ :>とが含まれているため、次と同等です。<:][

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

リテラル0xFULL0xF(の16進数15)と同じです。はULLそれがunsigned long longリテラルであることを指定してます。いずれの場合も、ブール値としてはtrueであるため、と0xFULL ? '\0' : -1評価されます'\0'。これは、数値が単にである文字リテラルです0

一方、0X.1P1は、2/ 16 = 0.125に等しい16進浮動小数点リテラルです。いずれの場合も、ゼロ以外であり、ブール値としても真であるため、!!再度2回否定するとが生成され1ます。したがって、全体が次のように簡略化されます。

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

演算子>>=は、右のオペランドで指定されたビット数だけ左のオペランドを右にビットシフトし、結果を返す複合代入です。この場合、右側のオペランドa[1]の値1は常になので、次と同等です。

while( a[0] >>= 1 )

または、同等に:

while( a[0] /= 2 )

の初期値a[0]は10です。1回右にシフトすると、5、次に(切り捨て)2、1、最後に0になり、ループが終了します。したがって、ループ本体は3回実行されます。


18
あなたは上で詳しく説明してもらえPの中で0X.1P1
わかりました-SEは邪悪です2014年

77
@Kay:それは同じだe10e5、あなたが使用する必要が除いて、pため進リテラルのe進数字です。
ディートリッヒエップ2014

9
@Kay:16進浮動小数点リテラルはC99の一部ですが、GCCはC ++コードでもそれらを受け入れます。ディートリッヒが指摘するpように、eは通常の科学的な浮動小数点表記と同様に、仮数と指数を分離します。1つの違いは、16進浮動小数点数の場合、指数部の底が10ではなく2であるため、0x0.1p10x0.1 = 1/16の2¹= 2に等しいことです(いずれの場合も、ここでは重要ではなく、ゼロ以外値はそこでも同様に機能します。)
Ilmari Karonen 14

6
@chux:どうやら、それコードがCとしてコンパイルされているのか、(元々タグ付けされていた)C ++としてコンパイルされているのかに依存します。しかし、「charリテラル」の代わりに「文字リテラル」と表示するようにテキストを修正し、Wikipediaリンクを追加しました。ありがとう!
Ilmari Karonen 14

8
素敵な削減。
コーリー

69

それはdigraphを含むいくつかのかなりあいまいなコードです。つまり<:、および:>[]それぞれとの代替トークンです。条件演算子の使用もあります。あり、ビットシフト演算、右シフトの割り当てが>>=

これはより読みやすいバージョンです:

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

さらに読みやすいバージョンで、の式[]を解決して、次の値に解決します。

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

交換a[0]a[1]同等のは、つまり、それらの値は、ループが何をしているかを把握することは容易にそれを行う必要がありますについて:

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

これは単純に各反復で2による(整数)除算を実行し、シーケンスを生成します5, 2, 1


私はそれを実行しませんでした-これはOPが得たの????ではなく、???を生成しませんか?(ええとcodepad.org/nDkxGUNi はを生成し???ます。
usr2564301 14

7
@Jongware 10は最初の反復で分割されました。したがって、ループによって評価される値は5、2、1、および0です。したがって、3回しか出力されません。
MysticXG 2014

42

左から右に式を見てみましょう:

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

最初に気づいたのは、の使用から三項演算子を使用していることです?。したがって、部分式:

0xFULL ? '\0' : -1

は、「0xFULLゼロ以外の場合はを返し'\0'、それ以外の場合は-10xFULLは、符号なしlong-longサフィックス付きの16進数リテラルですunsigned long long。つまり、のタイプの16進数リテラルであることを意味します。ただし、0xF通常の整数の内部に収まるので、実際には問題ではありません。

また、三項演算子は、第2項と第3項の型を共通の型に変換します。'\0'その後に変換されるintだけです、0

の値は0xFゼロよりかなり大きいので、合格です。式は次のようになります。

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

次は、:>ある有向グラフ。これは、次のように展開される構成です]

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

>>=符号付き右シフト演算子aです。スペースを空けて明確にすることができます。

さらに、次の<:ように拡張されるダイグラフです[

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

0X.1P1指数付きの16進リテラルです。しかし、値に関係なく、!!ゼロ以外のすべてのものは真です。0X.1P10.125非ゼロなので、次のようになります。

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

>>=符号付き右シフト演算子です。演算子の右側の値だけビットを前方にシフトすることにより、左のオペランドの値を変更します。10バイナリでは1010です。だからここにステップがあります:

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

>>=その演算の結果を返すためa[0]、ビットが1つ右にシフトされるたびにシフトがゼロ以外である限り、ループは継続します。4回目の試行は、a[0]がになる場所な0ので、ループには入りません。

その結果、?3回印刷されます。


3
:>ダイグラフであり、トリグラフではありません。これはプリプロセッサでは処理されず、単にと同等のトークンとして認識されます]
キーストンプソン

@KeithThompsonありがとう
0x499602D2 14

1
三項演算子(?:)には、第2項と第3項の共通の型である型があります。最初の項は常に条件付きで、タイプがありboolます。第2項と第3項の両方に型がintあるため、3項演算の結果はではintなくになりunsigned long longます。
コーリー

2
@KeithThompsonプリプロセッサで処理できます。プリプロセッサは、理由の有向グラフについて知っている必要があります#し、##有向グラフの形を持っています。初期の翻訳段階で実装が有向グラフを非有向グラフに翻訳するのを妨げるものは何もありません
MM

@MattMcNabbこれを知る必要があったのは久しぶりですが、IIRCは他の要件の結果として、pp-tokenがトークンに変換されるポイント(翻訳フェーズの最初)まで、ダイグラフはダイグラフ形式のままでなければなりません。 7)。
zwol 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.