私の会社には、メモリを解放した後、変数をNULLにリセットするというコーディング規則があります。例えば ...
void some_func ()
{
int *nPtr;
nPtr = malloc (100);
free (nPtr);
nPtr = NULL;
return;
}
上記のコードのような場合、NULLに設定しても意味がないと思います。それとも何か不足していますか?
そのような場合に意味がなければ、このコーディングルールを削除するために「品質チーム」に取り上げます。ご意見をお聞かせください。
未使用のポインターをNULLに設定することは防御的なスタイルであり、ぶら下がりポインターのバグから保護します。ダングリングポインターが解放された後にアクセスされた場合、ランダムメモリを読み取ったり上書きしたりできます。 NULLポインターにアクセスすると、ほとんどのシステムで即座にクラッシュし、エラーが何であるかがすぐにわかります。
ローカル変数の場合、ポインタが解放された後にアクセスされないことが「明らか」である場合、それは少し無意味かもしれません。したがって、このスタイルはメンバーデータとグローバル変数により適しています。ローカル変数の場合でも、メモリが解放された後も関数が継続する場合、適切なアプローチになる可能性があります。
スタイルを完成させるには、ポインターを真のポインター値に割り当てる前に、ポインターをNULLに初期化する必要もあります。
NULL
の後にfree
へのポインターを設定することは、疑わしい慣習であり、多くの場合、特許の誤った前提で「優れたプログラミング」ルールとして普及しています。 「正しい」カテゴリに属する偽の真実の1つですが、実際にはまったく有用なものは何も達成しません(そして、時には否定的な結果につながります)。
伝えられるところでは、NULL
の後にfree
にポインターを設定すると、同じポインター値がfree
に2回以上渡されたときの恐ろしい「二重解放」問題を防ぐことができます。しかし実際には、10個のうち9個の場合、同じポインター値を保持するdifferentポインターオブジェクトがfree
の引数として使用されると、実際の「ダブルフリー」問題が発生します。言うまでもなく、NULL
の後にfree
へのポインターを設定しても、このような場合に問題を防ぐための絶対的な効果はありません。
もちろん、free
への引数として同じポインターオブジェクトを使用すると、「ダブルフリー」問題が発生する可能性があります。ただし、実際のそのような状況では、通常、コードの一般的な論理構造に問題があることを示しており、単なる偶発的な「二重解放」ではありません。このような場合に問題に対処する適切な方法は、同じポインターがfree
に複数回渡される状況を回避するために、コードの構造を確認して再考することです。そのような場合、ポインターをNULL
に設定し、問題を「修正済み」と見なすことは、カーペットの下で問題を一掃する試みにすぎません。コード構造の問題は、それ自体を明示する別の方法を常に見つけるため、一般的なケースでは機能しません。
最後に、コードがNULL
またはNULL
ではないポインター値に依存するように特別に設計されている場合、NULL
の後にポインター値をfree
に設定することはまったく問題ありません。しかし、一般的な「グッドプラクティス」ルールとして(「NULL
の後に常にポインタをfree
に設定する」など)、これもまたよく知られた非常に役に立たない偽物であり、純粋に宗教的でブードゥー教のような理由でしばしば続くものです。
ほとんどの応答は二重解放の防止に焦点を合わせていますが、ポインターをNULLに設定することには別の利点があります。ポインタを解放すると、そのメモリはmallocの別の呼び出しで再割り当てできます。まだ元のポインターが残っている場合、解放後にポインターを使用して他の変数を破損しようとすると、プログラムが不明な状態に入り、あらゆる種類の悪いことが起こる可能性があります(クラッシュするとクラッシュする可能性があります「運がよければ、データが破損している場合は不運です)。解放後にポインターをNULLに設定した場合、後でそのポインターを介して読み取り/書き込みを試みると、セグメンテーション違反が発生します。これは、一般的にランダムなメモリ破損よりも望ましい方法です。
両方の理由から、free()の後にポインターをNULLに設定することをお勧めします。ただし、必ずしも必要なわけではありません。たとえば、ポインター変数がfree()の直後にスコープから外れた場合、NULLに設定する理由はあまりありません。
これは、メモリの上書きを避けるための良い習慣と考えられています。上記の機能では不要ですが、多くの場合、アプリケーションエラーを検出できます。
代わりに次のようなものを試してください。
#if DEBUG_VERSION
void myfree(void **ptr)
{
free(*ptr);
*ptr = NULL;
}
#else
#define myfree(p) do { void ** __p = (p); free(*(__p)); *(__p) = NULL; } while (0)
#endif
DEBUG_VERSIONを使用すると、デバッグコードで自由にプロファイルを作成できますが、どちらも機能的には同じです。
編集:do ...を追加しましたが、以下に示すように感謝します。
ポインタをfree
'dメモリに設定すると、未定義の動作を引き起こすのではなく、ポインタを介してそのメモリにアクセスしようとするとすぐにクラッシュします。物事がどこでうまくいかなかったかを判断するのがずっと簡単になります。
私はあなたの議論を見ることができます:nPtr
はnPtr = NULL
の直後に範囲外になるので、NULL
に設定する理由はないようです。ただし、struct
メンバーの場合、またはポインターがすぐにスコープ外に出ない他の場所の場合は、より意味があります。使用すべきでないコードがそのポインターを再び使用するかどうかは、すぐにはわかりません。
開発者がルールに従うことは言うまでもなく、ルールを自動的に施行することははるかに難しいため、これらの2つのケースを区別せずにルールが記述されている可能性があります。毎回無料でポインタをNULL
に設定しても問題ありませんが、大きな問題を指摘する可能性があります。
Free()dされたポインターに到達すると、壊れるかどうかがわかります。そのメモリはプログラムの別の部分に再割り当てされ、メモリが破損する可能性があります。
ポインターをNULLに設定してからアクセスすると、プログラムは常にセグメンテーション違反でクラッシュします。これ以上、時には動作することはありません」、もはや予測不可能な方法でクラッシュします」デバッグがずっと簡単です。
cの最も一般的なバグは、double freeです。基本的にあなたはそのようなことをします
free(foobar);
/* lot of code */
free(foobar);
そして、それはかなり悪い結果になり、OSはすでに解放されたメモリの一部を解放しようとし、通常はセグメンテーション違反を起こします。したがって、NULL
に設定することをお勧めします。そうすると、このメモリを本当に解放する必要があるかどうかをテストして確認できます。
if(foobar != null){
free(foobar);
}
free(NULL)
は何もしないので、ifステートメントを記述する必要がないことにも注意してください。私は実際にはOSの第一人者ではありませんが、ほとんどのOSはダブルフリーでクラッシュします。
これは、ガベージコレクションを備えたすべての言語(Java、dotnet)がこの問題を抱えておらず、開発者にメモリ管理全体を任せる必要がないことを誇りに思っている主な理由でもあります。
この背後にある考え方は、解放されたポインタの偶発的な再利用を停止することです。
これは実際に重要です。メモリを解放しますが、プログラムの後半部分で、何か新しいものが割り当てられ、そのスペースに着地する可能性があります。これで、古いポインタは有効なメモリチャンクをポイントするようになります。その場合、誰かがポインターを使用して、無効なプログラム状態になる可能性があります。
ポインターをNULLアウトすると、使用しようとすると0x0が間接参照され、そこでクラッシュします。これはデバッグが容易です。ランダムメモリを指すランダムポインターはデバッグが困難です。これは明らかに必要ではありませんが、ベストプラクティスドキュメントに記載されているのはそのためです。
私の経験では、解放されたメモリ割り当てにアクセスするとき、これはほとんど常に助けになると思います。どこかに別のポインタがあるためです。そして、それは「無駄な混乱を避ける」という別の個人的なコーディング標準と競合するので、コードを読みにくくすることはめったにないと思うので、私はそれをしません。
ただし、ポインターが再び使用されることを想定していない場合、変数をnullに設定することはありませんが、多くの場合、より高いレベルの設計では、nullに設定する理由が与えられます。たとえば、ポインターがクラスのメンバーであり、それが指すものを削除した場合、クラスの好きな場合は「契約」はそのメンバーがいつでも有効なものを指すため、nullに設定する必要がありますそのため。小さな違いですが、重要なことだと思います。
C++では、メモリを割り当てるときに、このデータを誰がownsかを常に考えることが重要です(スマートポインターを使用している場合でも、それでもある程度の考慮が必要です)。そして、このプロセスは、一般にあるクラスのメンバーであるポインターにつながる傾向があり、一般的にクラスを常に有効な状態にする必要があり、それを行う最も簡単な方法は、メンバー変数をNULLに設定してそれが指すことを示すことです今は何もない。
一般的なパターンは、コンストラクターですべてのメンバーポインターをNULLに設定し、デザインがクラスownsと言っているデータへのポインターでデストラクタ呼び出しdeleteを使用することです。明らかにこの場合、以前にデータを所有していないことを示すために何かを削除する場合は、ポインターをNULLに設定する必要があります。
要約すると、はい、私はしばしば何かを削除した後、ポインターをNULLに設定しますが、それはコーディングデザインのルールに盲目的に従うことではなく、より大きなデザインとデータの所有者に関する考えの一部です。私はあなたの例ではそうしません。そうすることには何の利点もないと思うので、私の経験ではこの種のものと同じくらいバグや悪いコードの原因となる「混乱」を追加します。
ANSI C標準から:
void free(void *ptr);
Free関数は、ptrが指すスペースを割り振り解除します。つまり、さらに割り振りできるようにします。 ptrがNULLポインターの場合、アクションは発生しません。そうではなく、引数がcalloc、malloc、またはrealloc関数によって以前に返されたポインターと一致しない場合、またはfreeまたはreallocの呼び出しによってスペースが割り当て解除された場合、動作は未定義です。
「未定義の動作」はほとんどの場合、プログラムのクラッシュです。これを避けるために、ポインターをNULLにリセットしても安全です。 free()自体は、ポインターへのポインターではなく、ポインターのみが渡されるため、これを行うことができません。ポインターをNULLにするより安全なバージョンのfree()を書くこともできます。
void safe_free(void** ptr)
{
free(*ptr);
*ptr = NULL;
}
最近、私は答えを探していた後に同じ質問に出くわしました。私はこの結論に達しました:
これはベストプラクティスであり、すべての(組み込み)システムで移植可能にするためには、これに従う必要があります。
free()
はライブラリ関数であり、プラットフォームを変更すると変化するため、この関数にポインターを渡した後、メモリを解放した後、このポインターがNULLに設定されることを期待しないでください。これは、プラットフォームに実装された一部のライブラリには当てはまらない場合があります。
だからいつも行く
free(ptr);
ptr = NULL;
このルールは、次のシナリオを回避しようとしている場合に役立ちます。
1)複雑なロジックとメモリ管理を備えた非常に長い関数があり、関数の後半で削除されたメモリへのポインタを誤って再利用したくない場合。
2)ポインターは、かなり複雑な動作をするクラスのメンバー変数であり、他の関数で削除されたメモリーへのポインターを誤って再利用したくない場合。
あなたのシナリオでは、それはあまり意味がありませんが、関数が長くなる場合は重要かもしれません。
NULLに設定すると、後で論理エラーが実際にマスクされる可能性があると主張するかもしれません。または、それが有効であると仮定した場合、NULLでクラッシュするため、問題ではありません。
一般的に、良いアイデアだと思うときはNULLに設定し、それが価値がないと思うときは気にしないようにアドバイスします。代わりに、短い関数と適切に設計されたクラスを書くことに集中してください。
2つの理由があります。
Cの最も一般的なバグは、double freeです。基本的にあなたはそのようなことをします
free(foobar); /* lot of code */ free(foobar);
そして、それはかなり悪い結果になり、OSはすでに解放されたメモリの一部を解放しようとし、通常はセグメンテーション違反を起こします。したがって、
NULL
に設定することをお勧めします。そうすると、このメモリを本当に解放する必要があるかどうかをテストして確認できます。if(foobar != NULL){ free(foobar); }
free(NULL)
は何もしないので、ifステートメントを記述する必要がないことにも注意してください。私は実際にはOSの第一人者ではありませんが、ほとんどのOSはダブルフリーでクラッシュします。それが、ガベージコレクションを備えたすべての言語(Java、dotnet)がこの問題を抱えておらず、開発者にメモリ管理全体を任せる必要がないことを誇りに思っている主な理由でもあります。
Martinv.Löwis a another answer 。
未使用のポインターをNULLに設定することは防御的なスタイルであり、ぶら下がりポインターのバグから保護します。ダングリングポインターが解放された後にアクセスされた場合、ランダムメモリを読み取ったり上書きしたりできます。 NULLポインターにアクセスすると、ほとんどのシステムで即座にクラッシュし、エラーが何であるかがすぐにわかります。
ローカル変数の場合、ポインタが解放された後にアクセスされないことが「明らか」であれば、それは少し無意味かもしれません。したがって、このスタイルはメンバーデータとグローバル変数により適しています。ローカル変数の場合でも、メモリが解放された後も関数が継続する場合、適切なアプローチになる可能性があります。
スタイルを完成させるには、ポインターをNULLに初期化してから、ポインターに真のポインター値を割り当てる必要があります。
アイデアは、それを解放した後、もはや有効ではないポインタを間接参照しようとすると、静かに、そして不可解にではなく、ハードに失敗する(セグメンテーション違反)ことです。
しかし...注意してください。 NULLを逆参照すると、すべてのシステムがセグメンテーション違反を引き起こすわけではありません。 (少なくとも一部のバージョンの)AIXでは、*(int *)0 == 0であり、SolarisはこのAIXの「機能」とオプションで互換性があります。
他の人が言ったことに追加するために、ポインターの使用法の1つの良い方法は、それが有効なポインターであるかどうかを常にチェックすることです。何かのようなもの:
if(ptr)
ptr->CallSomeMethod();
ポインタを解放した後に明示的にNULLとしてマークすると、C/C++でこの種の使用が可能になります。
これは、すべてのポインターを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
に割り当てると、関係のないすべての種類が上書きされ、いバグが発生する可能性があります。特に、コンパイラがデバッグモードでローカル変数をゼロで初期化するが、最適化がオンになると初期化されない場合。したがって、リリースビルドがランダムに爆発する間、デバッグビルドはバグの兆候を示しません...
解放されたばかりのポインタをNULLに設定することは必須ではありませんが、良い習慣です。このようにして、1)解放された先のとがったものを使用する2)それを2回解放することを避けることができます
NULLへのポインターの設定は、いわゆるダブルフリーを再び保護することです。これは、同じアドレスに対してfree()がそのアドレスでブロックを再割り当てせずに複数回呼び出される状況です。
ダブルフリーは未定義の動作につながります-通常、ヒープ破損またはプログラムの即時クラッシュ。 NULLポインターに対してfree()を呼び出しても何も行われないため、安全であることが保証されます。
したがって、free()の直後または非常にすぐにポインターがスコープから出ることが確実でない限り、free()が再度呼び出されてもNULLポインターと未定義の動作のために呼び出されるように、ポインターをNULLに設定することをお勧めします回避されます。
元の質問:内容を解放した後、ポインタを直接NULLに設定することは、コードがすべての要件を満たし、完全にデバッグされ、二度と変更されない限り、時間の無駄です。一方、解放されたポインターを防御的にNULLにすることは、誰かが無意識にfree()の下に新しいコードブロックを追加する場合、元のモジュールの設計が正しくない場合、およびその場合に非常に便利です。 -compiles-but-doesn-do-what-I-wantバグ。
どのシステムでも、正しいことを最も簡単にするという達成不可能な目標と、不正確な測定の削減できないコストがあります。 Cでは、熟練した労働者の手で多くのものを作成し、不適切に処理するとあらゆる種類の比phor的な負傷を負わせることができる非常に鋭く、非常に強力なツールのセットが提供されます。理解するのが難しい、または正しく使用するのが難しいものもあります。そして、自然にリスクを嫌う人々は、NULL値を探す前にポインターをチェックするなどの非合理的なことをします…
測定の問題は、良いものと悪いものを分けようとするたびに、ケースが複雑になるほど、あいまいな測定値が得られる可能性が高くなることです。目標がグッドプラクティスのみを維持することである場合、いくつかのあいまいなものは実際には良くないものに放り込まれます。あなたの目標が良くないものを排除することであるなら、曖昧さは良いものにとどまるかもしれません。良いだけを維持するか、明らかに悪いことを排除するかの2つの目標は正反対に思えますが、通常、一方でも他方でもない、両方の一部である3番目のグループがあります。
品質部門に申し立てる前に、バグデータベースを調べて、無効なポインター値が問題を引き起こした頻度を確認してください。本当の違いを作りたい場合は、実動コードで最も一般的な問題を特定し、それを防ぐ3つの方法を提案してください
NULLでポインタ変数を宣言することは常に推奨されます。たとえば、
int *ptr = NULL;
たとえば、ptrがx10メモリアドレスを指しているとします。 free(ptr)
を使用した後、NULLを再度宣言して、ポインター変数を無効にすることを常にお勧めします。例えば。:
free(ptr);
ptr = NULL;
NULLに再宣言されていない場合、ポインター変数は引き続き同じアドレスを指し続けます(x10)。このポインター変数はダングリングと呼ばれますポインター。別のポインター変数を定義し(たとえば、q)、新しいポインターにアドレスを動的に割り当てると、新しいポインターによって同じアドレス(x10)を取得する可能性があります変数。場合に、同じポインター(ptr)を使用し、同じポインター(ptr)が指すアドレスの値を更新すると、プログラムは最終的にqが指している場所の値(pとqは同じアドレスを指しているため(x10) )。
例えば.
*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.
長い話:誤って(誤って)解放したアドレスにアクセスしたくない。なぜなら、アドレスを解放すると、ヒープ内のそのアドレスを他のアプリケーションに割り当てることができるからです。
ただし、ポインターをNULLに設定していない場合、誤ってポインターを逆参照するか、そのアドレスの値を変更してください。あなたはまだそれをすることができます。しかし、あなたが論理的にしたいことは何もしません。
なぜ解放したメモリの場所にアクセスできるのですか?理由は次のとおりです。メモリを解放できたとしても、ポインタ変数にはヒープメモリアドレスに関する情報が残っていました。したがって、防御戦略として、NULLに設定してください。
品質保証チームが配置されているので、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要件を満たすことができます。