web-dev-qa-db-ja.com

nullポインターがすべてのビットがゼロではない場合にC / C ++コードを正しく記述する方法

comp.lang.c FAQ にあるように、nullポインターがすべてのビットがゼロではないアーキテクチャがあります。したがって、問題は実際に次の構成をチェックするものです。

void* p = get_some_pointer();
if (!p)
    return;

pをマシン依存のNULLポインターと比較していますか、それともpを算術ゼロと比較していますか?

書かなければならない

void* p = get_some_pointer();
if (NULL == p)
    return;

代わりに、そのようなアーキテクチャの準備ができているのか、それとも単なる私の妄想ですか?

69
ivaigult

C仕様によると:

値が0の整数定数式、またはvoid *型にキャストされる式は、nullポインター定数と呼ばれます。 55)NULLポインター定数がポインター型に変換される場合、NULLポインターと呼ばれる結果のポインターは、オブジェクトまたは関数へのポインターと等しくないものと比較されることが保証されます。

したがって、_0_はNULLポインター定数です。そして、それをポインター型に変換すると、一部のアーキテクチャでは非オールビットゼロになる可能性のあるヌルポインターを取得します。次に、ポインターとNULLポインター定数の比較に関する仕様の内容を見てみましょう。

一方のオペランドがポインターで、もう一方がNULLポインター定数である場合、NULLポインター定数はポインターのタイプに変換されます。

_(p == 0)_を考えてみましょう。最初の_0_がヌルポインターに変換され、次にpが実際のビット値がアーキテクチャに依存するヌルポインター定数と比較されます。

次に、否定演算子に関する仕様の説明を参照してください。

論理否定演算子の結果!オペランドの値が0と等しくない場合は0、オペランドの値が0と等しい場合は1です。結果の型はintです。式!Eは(0 == E)と同等です。

これは、_(!p)_が_(p == 0)_と同等であることを意味します。これは、仕様によれば、マシン定義のNULLポインター定数に対してpをテストします。

したがって、nullポインター定数がall-bits-zeroでないアーキテクチャでも、if (!p)を安全に記述できます。

C++の場合、ヌルポインター定数は次のように定義されます。

NULLポインター定数は、整数型の整数定数式(5.19)のprvalueで、ゼロまたはstd :: nullptr_t型のprvalueに評価されます。 NULLポインター定数は、ポインター型に変換できます。結果はそのタイプのNULLポインター値であり、オブジェクトポインターまたは関数ポインタータイプの他のすべての値と区別できます。

これは、Cの場合とnullptr構文シュガーに近いものです。演算子_==_の動作は、次によって定義されます。

さらに、メンバーへのポインター、またはメンバーへのポインターとNULLポインター定数を比較できます。メンバーへのポインター変換(4.11)および資格変換(4.4)は、それらを共通の型にするために実行されます。一方のオペランドがNULLポインター定数の場合、共通の型はもう一方のオペランドの型です。それ以外の場合、共通型は、オペランド型のcv修飾シグネチャの和集合であるcv修飾シグネチャ(4.4)を持つ、オペランドの1つの型に類似した(4.4)メンバー型へのポインタです。 [注:これは、メンバーへのポインターをNULLポインター定数と比較できることを意味します。 —終了ノート]

それは_0_のポインター型への変換につながります(Cの場合)。否定演算子の場合:

論理否定演算子のオペランド!文脈的にブールに変換されます(4節)。その値は、変換されたオペランドがtrueの場合はtrue、それ以外の場合はfalseです。結果の型はブールです。

つまり、_!p_の結果は、ポインターからboolへの変換の実行方法に依存します。標準は言う:

ゼロ値、nullポインター値、またはnullメンバーポインター値はfalseに変換されます。

したがって、if (p==NULL)if (!p)はC++でも同じことを行います。

100
ivaigult

実際のマシンでは、nullポインターが全ビット0であるかどうかは関係ありません。 pがポインターであると仮定すると:

if (!p) 

pがNULLポインターであるかどうかをテストするための常に有効な方法であり、常に次と同等です。

if (p == NULL)

別のC-FAQ記事に興味があるかもしれません: これは奇妙です。NULLは0であることが保証されていますが、NULLポインターはそうではありませんか?


上記はCとC++の両方に当てはまります。 C++(11)では、nullポインターリテラルにnullptrを使用することが推奨されます。

32
Yu Hao

この回答はCに適用されます。

NULLをNULLポインターと混同しないでください。 NULLは、nullポインター定数であることが保証された単なるマクロです。 NULLポインター定数は、_0_または_(void*)0_のいずれかであることが保証されています。

C11 6.3.2.3から:

値が0の整数定数式、またはそのような式をvoid *型にキャストしたものは、nullポインター定数と呼ばれます66)。 NULLポインター定数がポインター型に変換される場合、NULLポインターと呼ばれる結果のポインターは、オブジェクトまたは関数へのポインターと等しくないことを保証されます。

66)マクロNULLは、<stddef.h>(および他のヘッダー)でNULLポインター定数として定義されています。 7.19を参照してください。

7.19:

マクロは

ヌル

実装定義のヌルポインター定数に展開されます。

NULLの場合の実装定義は、_0_または_(void*)0_です。 NULLは他のものにはできません。

ただし、nullポインター定数がポインターに割り当てられると、nullポインターが返されます。これは、nullポインター定数と比較しても値がゼロにならない場合があります。コードif (!p)NULLマクロとは関係ありません。nullポインターを算術値ゼロと比較しています。

したがって、理論的には、_int* p = NULL_のようなコードは、ゼロとは異なるヌルポインターpになる可能性があります。

10
Lundin

かつて、STRATUSコンピュータにはすべての言語でNULLポインターが1でした。

これによりCに問題が発生したため、Cコンパイラは0と1のポインター比較でtrueを返すことができました

これにより、次のことが可能になります。

void * ptr=some_func();
if (!ptr)
{
    return;
}

デバッガーでreturnの値が1であることがわかりましたが、null ptrでptr

if ((void *)0 == (void *)1)
{
    printf("Welcome to STRATUS\n");
}

実際に「STRATUSへようこそ」を印刷しますか

7

コンパイラーが優れている場合、注意すべき点が2つ(そして2つだけ)あります。

1:静的なデフォルトで初期化された(つまり、割り当てられていない)ポインターにはNULLが含まれません。

2:構造体または配列のmemset()、または拡張機能によってcalloc()はポインターをNULLに設定しません。

1
Joshua