177

私の会社には、メモリを解放した後、変数をにリセットするというコーディング規則がありますNULL。例えば ​​...

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}

上記のような場合、に設定しNULLても意味がないと思います。それとも私は何かが足りないのですか?

そのような場合に意味がない場合は、「品質チーム」に相談して、このコーディングルールを削除します。ご意見をお聞かせください。

4

23 に答える 23

313

未使用のポインターをNULLに設定することは防御的なスタイルであり、ダングリングポインターのバグから保護します。解放された後にダングリングポインタにアクセスすると、ランダムメモリを読み取ったり上書きしたりできます。nullポインタにアクセスすると、ほとんどのシステムですぐにクラッシュし、エラーが何であるかがすぐにわかります。

ローカル変数の場合、解放された後にポインタがアクセスされなくなることが「明らか」である場合は少し意味がない可能性があるため、このスタイルはメンバーデータとグローバル変数に適しています。ローカル変数の場合でも、メモリが解放された後も関数が継続する場合は、良いアプローチになる可能性があります。

スタイルを完成させるには、真のポインター値が割り当てられる前に、ポインターをNULLに初期化する必要もあります。

于 2009-06-22T05:40:26.123 に答える
42

応答のほとんどは二重解放の防止に重点を置いていますが、ポインターを NULL に設定することには別の利点があります。ポインタを解放すると、そのメモリは malloc への別の呼び出しによって再割り当てできるようになります。元のポインターがまだ残っている場合、ポインターを解放した後にそのポインターを使用しようとして、他の変数が破損し、プログラムが不明な状態になり、あらゆる種類の悪いことが発生する可能性があるというバグが発生する可能性があります (運が良ければ、運が悪いとデータが破損します)。解放後にポインターを NULL に設定した場合、後でそのポインターを介して読み取り/書き込みを試みると、セグメンテーション違反が発生します。これは、通常、ランダムなメモリ破損よりも望ましい方法です。

どちらの理由からも、free() の後にポインターを NULL に設定することをお勧めします。ただし、常に必要というわけではありません。たとえば、ポインター変数が free() の直後にスコープ外になった場合、それを NULL に設定する理由はあまりありません。

于 2009-12-10T07:28:55.333 に答える
41

NULLafterへのポインターの設定freeは疑わしい慣行であり、明らかに誤った前提に基づいた「優れたプログラミング」ルールとして広く普及していることがよくあります。これは、「正しいように聞こえる」カテゴリに属する​​偽の真実の 1 つですが、実際には何の役にも立たない (時には否定的な結果につながることもあります)。

伝えられるところによると、ポインタをNULLafterに設定すると、同じポインタ値が複数回free渡されたときの恐ろしい「二重解放」問題を防ぐことが想定されています。freeただし、実際には、10 件中 9 件のケースで、同じポインター値を保持する異なるfreeポインター オブジェクトが の引数として使用されたときに、実際の「ダブル フリー」の問題が発生します。言うまでもなく、ポインターをNULLafterに設定してfreeも、このような場合の問題を防ぐことはまったくできません。

もちろん、同じポインタ オブジェクトを への引数として使用すると、「ダブル フリー」の問題が発生する可能性がありますfree。ただし、実際には、そのような状況は通常、コードの一般的な論理構造に問題があることを示しており、単なる偶発的な「ダブル フリー」ではありません。freeこのような場合の問題に対処する適切な方法は、コードの構造を見直して再考し、同じポインターが複数回渡される状況を回避することです。このような場合、ポインターを設定しNULLて問題を「修正済み」と見なすことは、問題をカーペットの下で一掃する試みにすぎません。コード構造の問題は常に別の方法で現れるため、一般的なケースでは機能しません。

NULL最後に、ポインター値が であるかどうかに依存するようにコードが特別に設計されている場合は、ポインター値をafterNULLに設定してもまったく問題ありません。しかし、一般的な「グッド プラクティス」のルールとして (「ポインターを常に後ろに設定する」など)、これもよく知られたかなり役に立たない偽物であり、純粋に宗教的でブードゥー教のような理由でいくつかの偽物が使用されることがよくあります。NULLfreeNULLfree

于 2009-12-10T08:27:43.693 に答える
20

これは、メモリの上書きを回避するための優れた方法と見なされます。上記の関数では不要ですが、実行するとアプリケーションエラーが見つかることがよくあります。

代わりに次のようなものを試してください。

#if DEBUG_VERSION
void myfree(void **ptr)
{
    free(*ptr);
    *ptr = NULL;
}
#else
#define myfree(p) do { void ** p_tmp = (p); free(*(p_tmp)); *(p_tmp) = NULL; } while (0)
#endif

DEBUG_VERSIONを使用すると、デバッグコードでフリーをプロファイリングできますが、どちらも機能的には同じです。

編集:以下に提案されているように、do...を追加しました。ありがとうございます。

于 2009-06-22T05:41:13.860 に答える
7

ポインタをfree'dメモリに設定すると、ポインタを介してそのメモリにアクセスしようとすると、未定義の動作が発生するのではなく、すぐにクラッシュします。どこがうまくいかなかったのかを判断するのがはるかに簡単になります。

私はあなたの議論を見ることができます:nPtr直後にスコープから外れるnPtr = NULLので、それをに設定する理由はないようですNULL。ただし、structメンバーまたはポインターがすぐにスコープから外れない他の場所の場合は、より理にかなっています。そのポインタが、それを使用すべきではないコードによって再び使用されるかどうかはすぐにはわかりません。

開発者がルールに従うことは言うまでもなく、ルールを自動的に適用することははるかに難しいため、これら2つのケースを区別せずにルールが記述されている可能性があります。解放するたびにポインタを設定しても問題はありませんがNULL、大きな問題を指摘する可能性があります。

于 2009-06-22T05:39:31.223 に答える
7

free()dされたポインタに到達すると、壊れるかどうかはわかりません。そのメモリがプログラムの別の部分に再割り当てされ、メモリが破損する可能性があります。

ポインタをNULLに設定した場合、ポインタにアクセスすると、プログラムは常にセグメンテーション違反でクラッシュします。これ以上、時々それは機能しません''、これ以上、予測できない方法でクラッシュします''。デバッグがはるかに簡単です。

于 2009-06-22T05:41:35.700 に答える
7

c で最も一般的なバグは double free です。基本的にあなたはそのようなことをします

free(foobar);
/* lot of code */
free(foobar);

OSはすでに解放されているメモリを解放しようとし、通常はセグメンテーション違反になります。したがって、 に設定するNULLことをお勧めします。これにより、このメモリを本当に解放する必要があるかどうかをテストして確認できます。

if(foobar != null){
  free(foobar);
}

free(NULL)また、何もしないので、if ステートメントを書く必要がないことに注意してください。私は OS の達人ではありませんが、今でもほとんどの OS はダブル フリーでクラッシュします。

これは、ガベージ コレクションを使用するすべての言語 (Java、dotnet) が、この問題がなく、メモリ管理全体を開発者に任せる必要がないことを非常に誇りに思っている主な理由でもあります。

于 2009-12-10T07:12:32.300 に答える
6

この背後にある考え方は、解放されたポインタの偶発的な再利用を防ぐことです。

于 2009-06-22T05:39:17.683 に答える
5

これは実際に重要です。メモリを解放しても、プログラムの後半で、スペースに着地する新しいものを割り当てることができます。古いポインタは、有効なメモリチャンクを指すようになります。その場合、誰かがポインタを使用して、プログラムの状態が無効になる可能性があります。

ポインタをNULLにすると、ポインタを使用しようとすると0x0が逆参照され、そこでクラッシュします。これはデバッグが簡単です。ランダムメモリを指すランダムポインタはデバッグが困難です。明らかに必要ではありませんが、それがベストプラクティスドキュメントに含まれている理由です。

于 2009-06-22T05:40:53.190 に答える
5

From the ANSI C standard:

void free(void *ptr);

The free function causes the space pointed to by ptr to be deallocated, that is, made available for further allocation. If ptr is a null pointer, no action occurs. Otherwise, if the argument does not match a pointer earlier returned by the calloc , malloc , or realloc function, or if the space has been deallocated by a call to free or realloc , the behavior is undefined.

"the undefined behavior" is almost always a program crash. So as to avoid this it is safe to reset the pointer to NULL. free() itself cannot do this as it is passed only a pointer, not a pointer to a pointer. You can also write a safer version of free() that NULLs the pointer:

void safe_free(void** ptr)
{
  free(*ptr);
  *ptr = NULL;
}
于 2009-12-10T07:18:47.610 に答える
5

最近、答えを探していたときに同じ質問に出くわしました。私はこの結論に達しました:

これはベスト プラクティスであり、すべての (組み込み) システムで移植可能にするには、これに従う必要があります。

free()プラットフォームの変更に応じて変化するライブラリ関数であるため、この関数にポインタを渡した後、メモリを解放した後、このポインタが NULL に設定されるとは思わないでください。これは、プラットフォーム用に実装された一部のライブラリには当てはまらない場合があります。

だからいつも行く

free(ptr);
ptr = NULL;
于 2012-10-09T14:25:11.600 に答える
4

私の経験では、人々が解放されたメモリ割り当てにアクセスするとき、ほとんどの場合、どこかに別のポインタがあるため、これはほとんど役に立ちません。そして、それは「無駄な混乱を避ける」という別の個人的なコーディング標準と競合するため、ほとんど役に立たず、コードが少し読みにくくなると思うので、私はそれをしません。

ただし、ポインターが再び使用されることが想定されていない場合は、変数を null に設定しませんが、より高いレベルの設計により、とにかく null に設定する理由が得られることがよくあります。たとえば、ポインターがクラスのメンバーであり、それが指すものを削除した場合、クラスの「契約」は、そのメンバーがいつでも有効なものを指すため、null に設定する必要があるということです。そのため。小さな違いですが、重要な違いだと思います。

C++ では、メモリを割り当てるときに、誰がこのデータを所有しているかを常に考えることが重要です (ただし、スマート ポインターを使用している場合を除きますが、その場合でも何らかの考慮が必要です)。そして、このプロセスは、通常、ポインターが何らかのクラスのメンバーになる傾向があり、通常、クラスを常に有効な状態にする必要があります。これを行う最も簡単な方法は、メンバー変数を NULL に設定して、それがポイントしていることを示すことです。今は何もありません。

一般的なパターンは、コンストラクターですべてのメンバー ポインターを NULL に設定し、クラスが所有していると設計で示されているデータへのポインターに対して、デストラクターが delete を呼び出すようにすることです。明らかに、この場合、何かを削除するときにポインタを NULL に設定して、以前にデータを所有していないことを示す必要があります。

要約すると、はい、何かを削除した後にポインターを NULL に設定することがよくありますが、それは、コーディング標準のルールにやみくもに従っているためではなく、より大きな設計とデータの所有者に関する考えの一部です。あなたの例ではそうしません。そうすることには利点がないと思うので、私の経験では、この種のことと同じようにバグや悪いコードの原因となる「混乱」が追加されます。

于 2009-12-10T08:11:12.060 に答える
3

2つの理由があります:

ダブルフリーするときのクラッシュを回避する

重複した質問でRageZによって書かれました。

cの最も一般的なバグは、ダブルフリーです。基本的にあなたはそのようなことをします

free(foobar);
/* lot of code */
free(foobar);

そしてそれはかなり悪い結果になります、OSはすでに解放されたメモリのいくつかを解放しようとします、そして一般的にそれはセグメンテーション違反です。したがって、に設定するNULLことをお勧めします。これにより、このメモリを本当に解放する必要があるかどうかをテストおよび確認できます。

if(foobar != NULL){
  free(foobar);
}

free(NULL) また、何も実行されないため、ifステートメントを記述する必要がないことにも注意してください。私は実際にはOSの第一人者ではありませんが、今でもほとんどのOSがダブルフリーでクラッシュするでしょう。

これは、ガベージコレクションを使用するすべての言語(Java、dotnet)がこの問題を抱えておらず、メモリ管理全体を開発者に任せる必要がないことを非常に誇りに思っている主な理由でもあります。

すでに解放されたポインタの使用は避けてください

別の答えマーティンv.Löwisによって書かれました。

未使用のポインターをNULLに設定することは防御的なスタイルであり、ダングリングポインターのバグから保護します。解放された後にダングリングポインタにアクセスすると、ランダムメモリを読み取ったり上書きしたりできます。nullポインタにアクセスすると、ほとんどのシステムですぐにクラッシュし、エラーが何であるかがすぐにわかります。

ローカル変数の場合、解放された後にポインタがアクセスされなくなることが「明らか」である場合は少し意味がない可能性があるため、このスタイルはメンバーデータとグローバル変数に適しています。ローカル変数の場合でも、メモリが解放された後も関数が継続する場合は、良いアプローチになる可能性があります。

スタイルを完成させるには、真のポインター値が割り当てられる前に、ポインターをNULLに初期化する必要もあります。

于 2009-12-10T07:49:57.467 に答える
3

このルールは、次のシナリオを回避しようとしている場合に役立ちます。

1)複雑なロジックとメモリ管理を備えた非常に長い関数があり、関数の後半で削除されたメモリへのポインタを誤って再利用したくない場合。

2)ポインタは、かなり複雑な動作をするクラスのメンバー変数であり、他の関数で削除されたメモリへのポインタを誤って再利用したくない場合。

あなたのシナリオでは、それはあまり意味がありませんが、関数が長くなる場合は、それが問題になる可能性があります。

NULLに設定すると、後で論理エラーが実際にマスクされる可能性があると主張する場合があります。または、有効であると想定した場合でも、NULLでクラッシュするため、問題ありません。

一般に、それが良い考えであると思うときはNULLに設定することをお勧めします。それが価値がないと思うときは、気にしないでください。代わりに、短い関数とうまく設計されたクラスを書くことに集中してください。

于 2009-06-22T05:42:25.650 に答える
3

解放されたばかりのポインターを NULL に設定することは必須ではありませんが、良い習慣です。このようにして、1) 解放された先のとがったものを使用することを避けることができます。 2) 2 回解放します。

于 2009-12-10T07:14:52.297 に答える
3

これは、すべてのポインターを NULL に初期化するための引数である可能性がありますが、次のようなものは非常に卑劣なバグになる可能性があります。

void other_func() {
  int *p; // forgot to initialize
  // some unrelated mallocs and stuff
  // ...
  if (p) {
    *p = 1; // hm...
  }
}

void caller() {
  some_func();
  other_func();
}

pスタック上の前者と同じ場所に配置されるnPtrため、一見有効なポインターがまだ含まれている可能性があります。に割り当てると、*pあらゆる種類の無関係なものが上書きされ、醜いバグが発生する可能性があります。特に、コンパイラがデバッグ モードでローカル変数を 0 で初期化するが、最適化が有効になった後はそうしない場合。そのため、デバッグ ビルドではバグの兆候が見られませんが、リリース ビルドではランダムに爆発します...

于 2009-06-22T05:59:46.547 に答える
2

他の人が言ったことに加えて、ポインタの使用法の1つの良い方法は、それが有効なポインタであるかどうかを常にチェックすることです。何かのようなもの:


if(ptr)
   ptr->CallSomeMethod();

ポインタを解放した後に明示的にNULLとしてマークすると、C /C++でこの種の使用が可能になります。

于 2009-06-22T05:44:33.450 に答える
2

NULL へのポインターを設定すると、いわゆる二重解放 (同じアドレスでブロックを再割り当てせずに同じアドレスに対して free() が複数回呼び出される状況) から保護されます。

二重解放は、未定義の動作につながります。通常、ヒープが破損するか、プログラムがすぐにクラッシュします。NULL ポインタに対して free() を呼び出しても何も起こらないため、安全であることが保証されています。

したがって、 free() の直後または直後にポインターがスコープを離れることが確実でない限り、ベストプラクティスは、そのポインターを NULL に設定して、 free() が再度呼び出された場合でも、NULL ポインターと未定義の動作に対して呼び出されるようにすることです。回避されます。

于 2009-12-10T07:16:09.923 に答える
2

元の質問へ: コードがすべての要件を満たし、完全にデバッグされ、二度と変更されない場合、内容を解放した直後にポインターを NULL に設定するのは完全に時間の無駄です。一方、解放されたポインターを防御的に NULL にすることは、誰かが無意識に free() の下に新しいコード ブロックを追加した場合、元のモジュールの設計が正しくない場合、およびその場合に非常に役立ちます。 -コンパイルしますが、やりたいことをしませんバグ。

どんなシステムにも、正しいことを最も簡単に行えるようにするという達成不可能な目標と、不正確な測定による削減不可能なコストがあります。C では、非常に鋭利で非常に強力なツールのセットが提供されます。これらのツールは、熟練した作業者の手に渡って多くのものを作成でき、不適切に扱うとあらゆる種類の比喩的な怪我を負わせる可能性があります。理解しにくい、または正しく使用できないものもあります。そして、自然にリスクを嫌う人々は、NULL 値のポインターをチェックしてから呼び出しを解放するなど、不合理なことを行います。

測定の問題は、良いものとそうでないものを区別しようとするときはいつでも、ケースが複雑になればなるほど、測定があいまいになる可能性が高くなることです。目標が良い慣行のみを維持することである場合、あいまいなものは実際には良くないものと一緒に捨てられます。あなたの目標が良くないものを排除することである場合、あいまいさは良いものにとどまるかもしれません. 2 つの目標、つまり、良いものだけを残すか、明らかに悪いものを排除するかは正反対のように見えますが、通常、どちらでもない 3 番目のグループがあり、両方のグループもあります。

品質部門に問題を提起する前に、バグ データベースを調べて、無効なポインター値が原因で問題が発生し、書き留める必要がある頻度を確認してください。真の違いを生み出したい場合は、本番コードで最も一般的な問題を特定し、それを防ぐ 3 つの方法を提案してください

于 2011-10-26T23:53:56.827 に答える
2

次のように、ポインタ変数をNULLで宣言することを常にお勧めします。

int *ptr = NULL;

たとえば、ptrが0x1000メモリ アドレスを指しているとします。を使用した後は、再度NULLfree(ptr)を宣言してポインター変数を無効にすることを常にお勧めします。例えば:

free(ptr);
ptr = NULL;

NULLに再宣言されていない場合、ポインタ変数は引き続き同じアドレス ( 0x1000 ) を指し続けます。このポインタ変数はダングリング ポインタと呼ばれます。別のポインター変数 (たとえば、q ) を定義し、新しいポインターにアドレスを動的に割り当てると、新しいポインター変数によって同じアドレス ( 0x1000 ) が取得される可能性があります。場合によっては、同じポインター ( ptr ) を使用し、同じポインター ( ptr )が指すアドレスの値を更新すると、プログラムはqが指している場所に値を書き込むことになります( pqは同じアドレスを指している (0x1000 )))。

例えば

*ptr = 20; //Points to 0x1000
free(ptr);
int *q = (int *)malloc(sizeof(int) * 2); //Points to 0x1000
*ptr = 30; //Since ptr and q are pointing to the same address, so the value of the address to which q is pointing would also change.
于 2016-09-03T13:01:09.477 に答える
1

品質保証チームが配置されているので、QA について少し補足させてください。C 用の自動 QA ツールの中には、解放されたポインタへの代入を「無用な代入」としてフラグを立てるものがありますptr。たとえば、Gimpel Software の PC-lint/FlexeLint は次のように述べています。 tst.c 8 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used

メッセージを選択的に抑制する方法があるため、チームが決定した場合でも、両方の QA 要件を満たすことができます。

于 2011-04-10T07:50:21.703 に答える