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