web-dev-qa-db-ja.com

((void *)0)はNULLポインター定数ですか?

私は このブログ記事 を読んでおり、セクションの下でヌルポインター定数と括弧で囲まれた式が参照している§6.3.2.3および§ISO C標準の6.5.1で、次のように書かれています:

does n'tは、括弧で囲まれたNULLポインター定数がNULLポインター定数であるとは言いません。

厳密に言えば、(void*)0はNULLポインター定数ですが、((void*)0)はNULLポインター定数ではありません。

次に:

ほとんどのC実装では、かっこで囲まれたNULLポインター定数をNULLポインター定数として扱い、NULL0((void*)0)、またはその他の方法で定義します。

参照されている2つのセクションには次のように記載されています。

§6.3.2.3

値が0の整数定数式、またはvoid *型にキャストされたそのような式は、nullポインター定数と呼ばれます。

§6.5.1

括弧で囲まれた式はプライマリ式です。 その型と値は括弧なしの式のものと同一です。括弧なしの式の場合、左辺値、関数指定子、またはvoid式です。それぞれ、左辺値、関数指定子、またはvoid式。

太字の文は、((void*)0)がNULLポインター定数ではないという著者の主張と矛盾していませんか?

34
user4164058

太字の文は、_((void*)0)_がNULLポインター定数ではないという著者の主張と矛盾していませんか?

いいえ、そうではありません。 (参照されているブログは私のものだからです。

太字の文は、そのtypeおよびvalueは括弧なしの式のものと同一であることを示しています。それがNULLポインター定数であることを暗示するためには十分ではありません。

考慮してください:

_void *var = 0;
_

_(void*)0_はNULLポインター定数です。 _((void*)0)_の型と値は_(void*)0_と同じです。 varalsoは_(void*)0_と同じ型と値を持っていますが、varは明らかにNULLポインター定数ではありません。

そうは言っても、意図は_((void*)0)_がNULLポインター定数であり、より一般的には、括弧で囲まれたNULLポインター定数はNULLポインター定数であると99%以上確信しています。規格の著者は、そう言うことを怠っただけです。また、6.5.1p5の括弧で囲まれた式の説明は、括弧で囲まれた式によって継承されるいくつかの他の特性を具体的に列挙しているため、

括弧で囲まれた式はプライマリ式です。その型と値は、括弧なしの式のものと同じです。括弧なしの式がそれぞれ左辺値、関数指定子、またはvoid式である場合、左辺値、関数指定子、またはvoid式です。

省略は厄介です(ただし、軽度の問題です)。

しかし、引数のために、_((void*)0)_はNULLポインター定数ではないと仮定しましょう。どんな違いがあるの?

_(void*)0_はNULLポインター定数であり、その値はタイプ_void*_のNULLポインターであるため、括弧で囲まれた式のセマンティクスによって_((void*)0)_はタイプ_void*_。 _(void*)0_と_((void*)0)_は両方ともアドレス定数です。 (まあ、私は思考それらです。)それで、どのコンテキストはnullポインター定数を必要とし、アドレス定数を受け入れませんか?ほんの少ししかありません。

6.5.9平等演算子

関数ポインター型の式は、ヌルポインター定数と等しいかどうか比較できます。 (オブジェクトポインターは_void*_型の式と比較できますが、関数ポインターはNULLポインター定数でない限り比較できません。)

_void func(void);
if (func == ((void*)0)) { /* ... */ }
_

制約違反になります。

6.5.16.1単純な割り当て

代入では、nullポインタ定数が関数へのポインタ型のオブジェクトに割り当てられ、暗黙的に変換されます。 NULLポインター定数ではない_void*_型の式は、関数ポインターに割り当てられない場合があります。引数の受け渡しと初期化にも同じ制約が適用されます。したがって、この:

_void (*fp)(void) = ((void*)0);
_

_((void*)0)_がNULLポインター定数でない場合、制約違反になります。これを見つけてくれたコメンターhvdに感謝します。

7.19共通の定義_<stddef.h>_

マクロNULLは、「実装定義のNULLポインター定数」に展開されます。 _((void*)0)_がNULLポインター定数でない場合、これは次のとおりです。

_#define NULL ((void*)0)
_

無効になります。これは、プログラマーではなく、実装に課せられる制限です。これに注意してください:

_#define NULL (void*)0
_

標準ヘッダーのマクロ定義は、必要に応じて括弧で完全に保護する必要があるため、間違いなく無効です(7.1.2p5)。括弧がなければ、有効な式_sizeof NULL_は構文エラーになり、sizeof (void*)の後に余分な定数_0_が続きます。

28
Keith Thompson

これは、nullポインター定数を含む括弧で囲まれた式であるため、紛れもなくnullポインター値です。これを右辺値として使用すると、「準拠」バージョンを右辺値として使用した場合とまったく同じ効果があります。

Nullポインター定数を受け入れるonly可能性のある構文ルールがある場合、修飾されません。しかし、私は何も知りません(Cの専門家ではありませんが)。

また、どちらもconstant(正式な文法生成を参照)ではありませんが、両方ともNULLポインター定数であるため、イニシャライザーの定数式に表示できますアドレス定数が許可されており、定数NULLポインター値がアドレス定数のカテゴリーに明示的に含まれています。

ポインターの比較では、特にNULLポインター定数についても言及していますが、ここではポインター値も受け入れられ、すべてのNULLポインター値は等しく扱われます。三項演算子と代入演算子についても同じです。

これらの規則はC++ではまったく異なることに注意してください。上記の式は両方ともvoid*型の定数NULLポインター値ですが、ユニバーサルNULLポインター定数ではありません。 C++のNULLポインター定数は整数定数式であり、ゼロに評価されます。また、void*は、暗黙的に他のポインター型に変換しません。

7
Ben Voigt