web-dev-qa-db-ja.com

CまたはC ++で「nullチェック」を行うとはどういう意味ですか?

私はC++を学習していて、nullを理解するのに苦労しています。特に、私が読んだチュートリアルでは「nullチェック」を行うことについて言及していますが、それが何を意味するのか、なぜそれが必要なのかはわかりません。

  • Nullとは正確には何ですか?
  • 「nullをチェックする」とはどういう意味ですか?
  • Nullを常にチェックする必要がありますか?

コード例をいただければ幸いです。

21
kdi

CおよびC++では、ポインターは本質的に安全ではありません。つまり、ポインターを間接参照する場合、有効な場所を指すようにするのはユーザー自身の責任です。これは、「手動メモリ管理」の一部です(Java、PHP、または.NETランタイムなどの言語で実装される自動メモリ管理スキームとは対照的です。これでは、かなりの労力なしに無効な参照を作成できません)。

多くのエラーをキャッチする一般的な解決策は、何も指していないすべてのポインターをNULL(または正しいC++では_0_)として設定し、ポインターにアクセスする前にそれを確認することです。具体的には、すべてのポインタをNULLに初期化し(宣言時にすでに指すポイントがない場合)、deleteまたはfree()のときにNULLに設定するのが一般的です。 (その後すぐに範囲外になる場合を除く)。例(Cでも有効なC++でも):

_void fill_foo(int* foo) {
    *foo = 23; // this will crash and burn if foo is NULL
}
_

より良いバージョン:

_void fill_foo(int* foo) {
    if (!foo) { // this is the NULL check
        printf("This is wrong\n");
        return;
    }
    *foo = 23;
}
_

Nullチェックがないと、この関数にNULLポインターを渡すとsegfaultが発生し、何もできません。OSはプロセスを強制終了し、コアダンプするか、クラッシュレポートダイアログをポップアップします。 nullチェックを配置すると、適切なエラー処理を実行して正常に回復できます-問題を自分で修正し、現在の操作を中止し、ログエントリを書き込み、ユーザーに通知します。

27
tdammers

他の答えはあなたの正確な質問をかなりカバーしました。 nullチェックは、受け取ったポインターが実際に型(オブジェクト、プリミティブなど)の有効なインスタンスを指していることを確認するために行われます。

ただし、ここに独自のアドバイスを追加します。 nullチェックを回避します。 :) nullチェック(および他の形式の防御的プログラミング)はコードを整理し、実際には他のエラー処理手法よりもエラーが発生しやすくします。

オブジェクトポインターに関しては、 Nullオブジェクトパターン を使用するのが私のお気に入りのテクニックです。つまり、nullではなく空の配列またはリスト(ポインター-またはそれ以上の参照)を返すか、nullの代わりに空の文字列( "")を返すか、文字列 "0"(または "nothing"と同等のもの)を返す"コンテキストで)整数に解析されることが期待される場所。

おまけとして、ここでは、(最初​​に正式に)C.A.R.によって実装された、nullポインタについてあなたが知らなかったかもしれない少しのことがあります。 1965年にALGOL W言語のHoare。

私はそれを10億ドルの間違いと呼んでいます。それは1965年にnull参照の発明でした。そのとき、私はオブジェクト指向言語(ALGOL W)での参照用の最初の包括的な型システムを設計していました。私の目標は、コンパイラーによって自動的に実行されるチェックにより、参照のすべての使用が絶対的に安全であることを確認することでした。しかし、実装が非常に簡単だったという理由だけで、null参照を挿入するという誘惑に抵抗できませんでした。これにより、無数のエラー、脆弱性、システムクラッシュが発生し、おそらく過去40年間で10億ドルの苦痛と損害を引き起こしています。

7
Yam Marcovic

Nullポインタ値は、明確に定義された「どこにもない」ことを表します。 無効ポインタ値であり、他のポインタ値と等しくないことが保証されています。 nullポインターを逆参照しようとすると、動作が未定義になり、通常は実行時エラーが発生するため、逆参照する前にポインターがNULLでないことを確認する必要があります。多くのCおよびC++ライブラリ関数は、エラー状態を示すためにnullポインターを返します。たとえば、ライブラリ関数mallocは、要求されたバイト数を割り当てることができない場合はnullポインター値を返し、そのポインターを介してメモリにアクセスしようとすると、(通常)ランタイムエラーが発生します。

int *p = malloc(sizeof *p * N);
p[0] = ...; // this will (usually) blow up if malloc returned NULL

したがって、mallocの値をNULLと照合して、pの呼び出しが成功したことを確認する必要があります。

int *p = malloc(sizeof *p * N);
if (p != NULL) // or just if (p)
  p[0] = ...;

今、あなたの靴下をちょっと待ってください、これは少しでこぼこになるでしょう。

Nullポインターとnullポインター定数があり、2つは必ずしも同じではありません。 nullポインタvalueは、基礎となるアーキテクチャが「どこにもない」ことを表すために使用する値です。この値は、0x00000000、0xFFFFFFFF、0xDEADBEEF、またはまったく別の値になる場合があります。 nullポインタvalueが常に0であると想定しないでください。

Nullポインター定数、OTOHは常に0値の積分式です。 ソースコードに関する限り、0(または0と評価される任意の整数式)はnullポインターを表します。 CとC++はどちらも、NULLマクロをnullポインター定数として定義しています。コードがコンパイルされると、生成されたマシンコードでは、nullポインターconstantが適切なnullポインターvalueに置き換えられます。

また、NULLは多くの可能なinvalidポインタ値の1つにすぎないことに注意してください。次のように、明示的に初期化せずに自動ポインタ変数を宣言した場合

int *p;

変数に最初に格納される値は不定であり、有効またはアクセス可能なメモリアドレスに対応していない場合があります。残念ながら、それを使用する前に、NULL以外のポインタ値が有効かどうかを判断する(移植可能な)方法はありません。したがって、ポインターを扱う場合、通常、ポインターを宣言するときに明示的にNULLに初期化し、ポインターがアクティブにポイントしていない場合はNULLに設定することをお勧めします。

これはC++よりもCの方が問題であることに注意してください。慣用的なC++は、それほど多くのポインタを使用するべきではありません。

4
John Bode

いくつかの方法があり、すべて基本的に同じことを行います。

 int * foo = NULL; //時々NULL 
ではなく0x00または0または0Lに設定

nullチェック(ポインタがnullかどうかをチェック)、バージョンA

 if(foo == NULL)

nullチェック、バージョンB

 if(!foo)// NULLは0として定義されているため、!fooはnullポインタから値を返します

nullチェック、バージョンC

 if(foo == 0)

3つのうち、最初のチェックを使用することをお勧めします。これは、チェックしようとしていることを将来の開発者に明示的に通知するためです。また、fooがポインターであると期待していることが明確になります。

3
user53019

あなたはしません。 C++でポインターを使用する唯一の理由は、ヌルポインターの存在を明示的に必要とするためです。それ以外の場合は、参照を使用できます。これは、意味的に使いやすく、nullでないことを保証します。

2
DeadMG