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


21

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

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

どんなコード例でも大歓迎です。



あなたが読んだすべての人がnullチェックについて説明し、サンプルコードを提供することなく話す場合は、いくつかのより良いチュートリアルを取得することをお勧めします
underscore_d

回答:


26

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ポインターを渡すとセグメンテーション違反が発生し、何もすることはできません。OSは単にプロセスを強制終了し、場合によってはコアダンプするか、クラッシュレポートダイアログを表示します。ヌルチェックを設定すると、適切なエラー処理を実行して適切に回復できます。問題を自分で修正し、現在の操作を中止し、ログエントリを書き込み、ユーザーに通知します。


3
@MrListerどういう意味ですか、nullチェックはC ++では機能しませんか?宣言するときに、ポインタをnullに初期化する必要があります。
TZHX

1
つまり、ポインタをNULLに設定することを忘れないでください。そうしないと機能しません。そして、覚えていれば、言い換えれば、ポインターがNULLであることを知っていれば、とにかくfill_fooを呼び出す必要はありません。fill_fooは、ポインターに有効な値があるかどうかではなく、ポインターに値があるかどうかを確認します。C ++では、ポインターがNULLまたは有効な値を持つことは保証されていません。
ミスターリスター

4
ここでは、assert()がより良い解決策になります。「安全に」しようとする意味はありません。NULLが渡された場合、明らかに間違っているので、プログラマーに完全に気付かせるために明示的にクラッシュしないのはなぜですか?(実稼働環境では、誰もfill_foo()をNULLで呼び出さないことが証明されているため、問題ではありませんか?実際、それほど難しくはありません。)
Ambroz Bizjak

7
この関数のさらに優れたバージョンでは、ポインターの代わりに参照を使用する必要があり、NULLチェックが廃止されることを忘れないでください。
Doc Brown

4
これは、手動のメモリ管理とは異なり、null参照を逆参照しようとすると、マネージプログラムも爆発します(または、ほとんどの言語のネイティブプログラムと同様に、少なくとも例外が発生します)。
メイソンウィーラー

7

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

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

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

おまけとして、1965年にCAR HoareがAlgol W言語用に(最初に)実装したヌルポインターについて、あなたが知らなかったかもしれないちょっとしたものがあります。

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


6
Nullオブジェクトは、単にNULLポインターを持つよりもさらに悪いです。アルゴリズムXに必要なデータYが必要でない場合、それはプログラムのバグであり、実行しているふりをして単に隠れています。
-DeadMG

それはコンテキストに依存し、どちらの方法でも「データの存在」をテストすることは、私の本でヌルをテストすることに勝ります。私の経験から、たとえばリストでアルゴリズムが動作し、リストが空の場合、アルゴリズムは何もすることはなく、for / foreachなどの標準制御ステートメントを使用するだけでそれを実現します。
ヤムマルコビッチ

アルゴリズムに何の関係もない場合、なぜそれを呼び出すのですか?そして、そもそもそれを呼び出したいと思う理由は、それが何か重要なことをするからです。
-DeadMG

@DeadMGプログラムは入力に関するものであり、実世界では宿題の割り当てとは異なり、入力は無関係(空など)になる可能性があります。コードはどちらの方法でも呼び出されます。2つのオプションがあります。関連性(または空)を確認するか、条件ステートメントを使用して関連性を明示的に確認せずに読み取り、適切に動作するようにアルゴリズムを設計します。
ヤムマルコビッチ

ほぼ同じコメントをするためにここに来たので、代わりに投票をしました。ただし、これはゾンビオブジェクトの大きな問題の代表であると付け加えます。マルチステージ初期化(または破壊)があり、完全にライブではないが完全に死んでいないオブジェクトがある場合はいつでも。オブジェクトが破棄されたかどうかを確認するためにすべての関数にチェックを追加した決定論的なファイナライズのない言語で「安全な」コードを見ると、それはこの一般的な問題です。if-nullを使用しないでください。ライフタイムに必要なオブジェクトを持つ状態を操作する必要があります。
ex0du5

4

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

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

したがって、NULLに対するmalloc値をチェックして、呼び出しが成功したことを確認する必要がありpます。

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

さて、靴下をちょっと待ってください、これは少しでこぼこになります。

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

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

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

int *p;

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

これはC ++ではなくCの問題であることに注意してください。慣用的なC ++はあまりポインターを使用すべきではありません。


3

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

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つのうち、最初のチェックを使用することを好みます。これは、将来の開発者にあなたがチェックしようとしていたことを明示的に伝えるためです。


2

あなたはしません。C ++でポインターを使用する唯一の理由は、nullポインターの存在が明示的に必要だからです。それ以外の場合は、参照を使用できます。これは、セマンティックに使いやすく、非nullを保証します。


1
@James:カーネルモードで「新規」ですか?
ネマンジャトリフノビッチ

1
@James:C ++プログラマーの大多数が享受する機能を表すC ++の実装。これには、すべての C ++ 03言語機能(を除くexport)と、すべてのC ++ 03ライブラリ機能 TR1 、および C ++ 11のかなりの部分が含まれます。
DeadMG

5
は、人々が「参照は非ヌルを保証する」と言わないことを望みます。彼らはしません。nullポインターと同じくらい簡単にnull参照を生成でき、同じように伝播します。
mjfgates

2
@Stargazer:言語設計者や優れた実践が推奨する方法でツールを使用するだけでは、質問は100%冗長です。
DeadMG

2
@DeadMG、冗長であるかどうかは関係ありません。あなたは質問に答えなかった。もう一度言います:-1。
リウォーク

-1

NULL値をチェックしない場合、特に、それが構造体へのポインタである場合、セキュリティの脆弱性-NULLポインタの逆参照に遭遇する可能性があります。NULLポインターの逆参照は、バッファーオーバーフロー、競合状態など、他の深刻なセキュリティ脆弱性につながる可能性があります。これにより、攻撃者がコンピューターを制御できるようになります。

Microsoft、Oracle、Adobe、Appleなどの多くのソフトウェアベンダーは、これらのセキュリティ脆弱性を修正するソフトウェアパッチをリリースしています。各ポインタのNULL値を確認する必要があると思います:)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.