マクロで整数定数式を見つけます[Linusと一緒に]

Linux Kernel Mailing Listからの論争の的となっているアイデアに関する最近の手紙の翻訳に注目してください。これはLinus Torvaldsの伝統的な反応を引き起こしました。 必要な説明は 、投稿の最後に記載されています。

手紙

送信者:マーティンワッカー
日付: 2018年3月20日火曜日22:13:35 +0000
トピック:マクロでの整数定数式の検出
こんにちはLinus

私はアイデアを得ました:

整数定数式ICE )自体を返す整数定数式のテスト。これは__builtin_choose_exprに渡すのに適しているはずで、次のようになります。

 #define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1))) 

ちなみに、この式では、 x自体はgccで評価されませんが、これは標準では保証されていません(古いバージョンのgccではこの事実を確認しませんでした)。

リーナス・トーバルズ回答

送信者: Linus Torvalds <>
日付: 2018年3月20 日(火)16:08:30 -0700
件名: Re:マクロでの整数定数式の検出
2018年3月20日火曜日、午後3時13分、マーティンウェイカー
<Martin.Uecker@med.uni-goettingen.de>はこう書いています:
私はアイデアを得ました:
いいえ、これは「アイデア」ではありません。
これは天才の仕事か、頭が完全に病気のどちらかです。
まだ完全には定かではないので、正確に言うことはできません。
整数定数式自体を返す整数定数式のテスト。これは__builtin_choose_exprに渡すのに適しているはずで、次のようになります。

 #define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1))) 
OK、ここでxICEときに(void *)((x)*0l))NULLになることが(void *)((x)*0l)) NULL 。 いいね 定数を使用すると:

 sizeof( 1 ? NULL : (int *) 1) 

そして、ここでの規則は次のとおりです。ポインターを持つ三項演算子の辺の1つがNULL場合、最終結果は異なる型(int *)ます。

そう、はい、上の式はsizeof(int)返します。

また、ICEでない場合、最初のポインターは(void *)型のままですが、 NULLはありません。

そして、はい、それぞれがNULLでない2つのポインターを持つ三項演算子の型キャスト規則は異なりNULL 。したがって、 "void *"返します。

したがって、最終結果は(sizeof(*(void *)(x))になります。これは、 gccでは通常intとは異なります。

そこで、ここで2つの問題を観察しています。


ただし、これらの問題は両方ともそれほど重要ではない可能性があり、これはすべて標準である可能性があります。
ちなみに、この式では、 x自体はgccでは評価されませんが、これは標準では保証されていません(古いバージョンのgccではこれを確認しませんでした)。
ああ、私にとっては、 sizeof()演算子が引数の値を計算するのではなく、その型のみを計算することを保証するのは標準です。

私はあなたの本当に驚くべき、嫌な「ハック」に喜んでいます。 それは本当の芸術作品です。

さまざまな理由でこれが機能しないか、警告を発生させると確信していますが、
まだ完璧です。

ライナス

説明


このコードで何が起こっているのかを理解してみましょう。

 #define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1))) 

マクロICE_P(x)を定義します。 Pは、命名規則によれば、 簡単な述語です。 ICEは整数定数式を表します。 xが整数定数式の場合はtrueを返し、それ以外の場合はfalseを返しtrue

この式は、比較の右側がsizeof(int)と等しい場合にtrueなりtrue 。 デプロイしてみましょう。

 sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)) 

この式は、3項式が指す型のサイズを返します。 より深く掘ります。

 1 ? ((void*)((x) * 0l)) : (int*)1 

もちろん、1は常にtrueなので、左側は常に戻りtrue 。 Linusが説明するように、 xがICEの場合、左側はNULLになりNULL 。 次の2つのオプションがあります。

xがICEの場合: 1 ? ((void*)(NULL)) : (int*)1 1 ? ((void*)(NULL)) : (int*)1
xがICEでない場合: 1 ? ((void*)(NOT-NULL)) : (int*)1 1 ? ((void*)(NOT-NULL)) : (int*)1

唯一の違いは、左側のvoid*NULLかどうかです。

NULL (xはICE)の場合、式はint*型を返しますint*
NULLでない場合(xはICEではありません)、式はvoid*返しvoid*

基本的に、三項式はNULL void *int *に変換できNULL void *が、 void *NULLでない場合、代わりにint * void *int * void * 。 これで元の式に戻ることができ、次の結果が得られます。

xがICEの場合: sizeof(int) == sizeof(*(int *))
xがICEでない場合: sizeof(int) == sizeof(*(void *))

void *の逆参照は有効な操作ではありませんが、 sizeofは魔法であり、コンパイル時に完全に計算されます。 gccでは、コードsizeof(*(void *)) 1になります。

以下に、このマクロをテストするためのサンプルコードicep.cます。

 /*   : gcc icep.c -o icep && ./icep  : $ gcc icep.c -o icep && ./icep ICE_P(1): 1 ICE_P('c'): 1 ICE_P(rand()): 0 */ #include <stdio.h> #include <stdlib.h> #define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1))) #define CHECK(x) printf("ICE_P(%s): %d\n", #x, ICE_P(x)) int main() { CHECK(1); CHECK('c'); CHECK(rand()); return 0; } 


追加説明


ここでのキー式は、ちょうどx * 0です。 xが整数定数の場合、コンパイラーは計算を実行でき、ゼロの整数はゼロです。 xが整数定数でない場合、コンパイラはこの計算を実行できず、ゼロであるかどうかはわかりません。 この結果はvoidポインターにキャストされます 。 これは、 NULLかどうかを調べる方法です(ゼロへのvoidポインターNULLの定義であるため)。

この式を理解するためのもう1つの鍵は、タイプa ? b : c a ? b : c bcは異なる型を持つことができることは明らかであり、この場合、コンパイラはこれらの式の「共通」型を把握する必要があります。 ここで、 cintへの明示的なポインターです。 ただし、 NULL他のタイプのポインターNULL互換性があります。 したがって、 bNULL場合、ジェネリック型は両方の式を記述するため、 int*です。 ただし、 b NULLかどうかが静的に不明な場合、 void*およびint* void*唯一の型はvoid*です。

これにより、 xが整数定数式でない場合はsizeof(*(void*))xが整数定数式である場合はsizeof(*(int*))します。

Source: https://habr.com/ru/post/J352322/


All Articles