手紙
送信者:マーティンワッカーこんにちはLinus
日付: 2018年3月20日火曜日22:13:35 +0000
トピック:マクロでの整数定数式の検出
私はアイデアを得ました:
整数定数式 ( 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に渡すのに適しているはずで、次のようになります。OK、ここで
#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))
x
が
ICE
ときに
(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つの問題を観察しています。
-
"sizeof(*(void *)1)"
必ずしも厳密に定義されて"sizeof(*(void *)1)"
わけで"sizeof(*(void *)1)"
ません。 gccの場合、これは1です。これにより、警告が発生する可能性があります。 - この表現をキャッチするすべての人の脳を破壊します。
ただし、これらの問題は両方ともそれほど重要ではない可能性があり、これはすべて標準である可能性があります。
ちなみに、この式では、ああ、私にとっては、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
b
と
c
は異なる型を持つことができることは明らかであり、この場合、コンパイラはこれらの式の「共通」型を把握する必要があります。 ここで、
c
は
int
への明示的なポインターです。 ただし、
NULL
他のタイプのポインター
NULL
互換性があります。 したがって、
b
が
NULL
場合、ジェネリック型は両方の式を記述するため、
int*
です。 ただし、
b NULL
かどうかが静的に不明な場合、
void*
および
int*
void*
唯一の型は
void*
です。
これにより、
x
が整数定数式でない場合は
sizeof(*(void*))
、
x
が整数定数式である場合は
sizeof(*(int*))
します。