解放後に変数をNULLに設定


156

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

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}

上記のコードのような場合、に設定しNULLても意味がないと思います。それとも何か不足していますか?

そのような場合に意味がない場合は、このコーディングルールを削除するために「品質チーム」に取り上げます。アドバイスをお願いします。


2
それをptr == NULL使って何かをする前に、それが何かをチェックできることは常に役に立ちます。解放されたポインタを無効にしないと、取得ptr != NULLできますが、まだ使用できないポインタです。
KiJéy2016

ダングリングポインターは、 Use-After-Freeなどの悪用可能な脆弱性につながる可能性があります。
КонстантинВан

回答:


285

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

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

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


3
「真のポインター値が割り当てられる前にポインターをNULLに初期化する」理由がわかりませんか?
ポールビガー

26
@Paul:特定のケースでは、宣言はを読み取ることができますint *nPtr=NULL;。さて、次の行でmallocが続くので、これは冗長であることに同意します。ただし、宣言と最初の初期化の間にコードがある場合、まだ値がないにもかかわらず、誰かが変数の使用を開始する可能性があります。nullで初期化すると、segfaultが発生します。せずに、ランダムメモリを再度読み書きする可能性があります。同様に、後で変数が条件付きでのみ初期化される場合、null初期化を覚えていれば、後で誤ってアクセスするとすぐにクラッシュします。
Martin v。Löwis09年

1
個人的には、自明ではないコードベースでは、nullを逆参照するためのエラーを取得することは、所有していないアドレスを逆参照するためのエラーを取得するのと同じくらいあいまいだと思います。私は個人的に気にすることはありません。
wilhelmtell 2010

9
ウィルヘルム、要点は、nullポインター逆参照を使用すると、明確なクラッシュと問題の実際の場所が表示されることです。不正アクセスはクラッシュする場合としない場合があり、予期しない場所で予期しない方法でデータまたは動作を破壊します。
アミットナイドゥ、2011

4
実際、NULLへのポインターの初期化には、少なくとも1つの重大な欠点があります。初期化されていない変数についてコンパイラーが警告するのを防ぐことができます。コードのロジックが実際に明示的にポインターの値を処理しない限り(つまり、(nPtr == NULL)が何かを行う場合...)そのままにしておくことをお勧めします。
Eric

37

NULL後へのポインタの設定freeことは疑わしい慣行であり、特許を誤った前提で「適切なプログラミング」ルールとして一般化することがよくあります。これは、「正しいサウンド」のカテゴリに属する​​偽の真実の1つですが、実際には、まったく何も役に立たない(場合によっては、否定的な結果につながる)こともあります。

伝えられるところNULLによるfreeと、ポインターをafter に設定することで、同じポインター値がfree複数回渡された場合の恐ろしい「二重解放」の問題を防ぐことができます。ただし、実際には、10のうち9つのケースで、同じポインタ値を保持するさまざまなポインタオブジェクトがの引数として使用されると、実際の「二重解放」の問題が発生しfreeます。言うまでもなく、ポインタをNULLafterに設定しても、freeそのような場合の問題を防ぐための絶対的な効果はありません。

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

最後に、コードがポインター値のNULL有無に依存するように特別に設計されている場合はNULL、ポインター値をNULLafter に設定しても問題ありませんfree。しかし、一般的な「良い習慣」のルールとして(「常にNULL後へのポインターを設定するfree」のように)、これもまた、よく知られており、かなり役に立たない偽物であり、純粋に宗教的で、ブードゥー教のような理由で続くことがよくあります。


1
間違いなく。解放後にポインターをNULLに設定することで修正されるdouble-freeを引き起こしたことを覚えていませんが、そうしないことをたくさん引き起こしました。
LnxPrgr3

4
@AnT "dubious"は少し多めです。それはすべてユースケースに依存します。ポインターの値が真/偽の意味で使用されている場合、それは有効なプラクティスであるだけでなく、ベストプラクティスでもあります。
コーダー、2015

1
@Coder完全に間違っています。ポインタの値を真の意味で使用して、解放の呼び出しの前にポインタがオブジェクトを指しているかどうかを知ると、ベストプラクティスだけでなく、誤りでもあります。例:foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;。ここでは、設定barNULL呼び出した後、free関数は、それが思うようになります決してバーがありませんでしたし、間違った値を返します!
David Schwartz

主な利点は二重解放から保護することではなく、ぶら下がりポインタをより早く、より確実にキャッチすることです。たとえば、リソース、割り当てられたメモリへのポインター、ファイルハンドルなどを保持する構造体を解放するとき、含まれているメモリポインターを解放して含まれているファイルを閉じると、それぞれのメンバーにNULLが設定されます。次に、ダングリングポインターを介してリソースの1つに誤ってアクセスすると、プログラムは毎回、すぐに障害が発生する傾向があります。そうしないと、NULL化しないと、解放されたデータがまだ上書きされず、バグを簡単に再現できない可能性があります。
ジムハーク2018

1
よく構造化されたコードでは、ポインタが解放された後にアクセスされる場合や、2回解放される場合を許可しないことに同意します。しかし、現実の世界では、私のコードは、おそらく私を知らず、適切に物事を行うための時間やスキルを持っていない誰かによって変更または管理されます(締め切りは常に昨日であるため)。したがって、私は誤用されてもシステムをクラッシュさせない防弾機能を書く傾向があります。
mfloris

34

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

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


1
+1これは実際には非常に良い点です。「ダブルフリー」(完全に偽物)についての推論ではありませんが、これは。私はの後のポインタの機械的なNULL化のファンではありませんfreeが、これは実際に理にかなっています。
AnT、2009

同じポインターを介して解放した後にポインターにアクセスできた場合、他のポインターを介して指しているオブジェクトを解放した後にポインターにアクセスする可能性がさらに高くなります。したがって、これはまったく役に立ちません-あるポインタを介してオブジェクトにアクセスした後、別のポインタを介してオブジェクトにアクセスしないようにするには、他のメカニズムを使用する必要があります。同じ方法で保護するためにその方法を使用することもできます。
David Schwartz

1
@DavidSchwartz:私はあなたのコメントに同意しません。数週間前に大学の演習用のスタックを書かなければならなかったとき、私は問題を抱えていたので、数時間調査しました。ある時点で、すでに解放されているメモリにアクセスしました(解放された行が早すぎました)。そして時にはそれは非常に奇妙な行動につながります。ポインターを解放した後でNULLに設定した場合、「単純な」セグメンテーションフォールトが発生し、数時間の作業を節約できただろう。したがって、この回答の+1です!
mozzbozz

2
@katze_sonne止まった時計でも1日2回は正しいです。ポインターをNULLに設定すると、既に解放されたオブジェクトへの誤ったアクセスがNULLをチェックし、チェックする必要のあるオブジェクトのチェックに失敗するコードでのセグメンテーション違反を防ぐことにより、バグを隠す可能性がはるかに高くなります。(おそらく、特定のデバッグに自由後NULLへのポインタを設定すると役に立つこと、または意味をなすかもしれないセグメンテーション違反に保証されているNULL以外の値にそれらを設定する可能性が構築されます。しかし、この愚かさは、一度あなたを助けるために起こっていることであるではないその賛成の引数。)
David Schwartz

@DavidSchwartzまあ、それは合理的に聞こえます...あなたのコメントのおかげで、私は将来これを検討します!:) +1
mozzbozz 2014年

20

これは、メモリの上書きを回避するための良い方法と考えられています。上記の関数では不要ですが、実行するとアプリケーションエラーが見つかる場合があります。

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

#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 ...を追加しました。ありがとうございます。


3
かっこなしのifステートメントの後にマクロバージョンを使用すると、マクロバージョンに微妙なバグが発生します。
マークランサム

(ボイド)0とは何ですか?このコードは次のことを行います:if(x)myfree(&x); それ以外はdo_foo(); if(x){free(*(&x)); *(&x)= null; } void 0; それ以外はdo_foo(); elseはエラーです。
jmucchiello 09年

そのマクロは、コンマ演算子の完璧な場所です:free((p))、*(p)= null。もちろん、次の問題は*(p)を2回評価することです。{void * _pp =(p); free(* _ pp); * _pp = null; }プリプロセッサは楽しいものではありません。
jmucchiello 09年

5
マクロは括弧で囲まないでください。壊れないdo { } while(0)ようにブロックで囲む必要がありますif(x) myfree(x); else dostuff();
Chris Lutz、

3
Lutzが言ったように、マクロボディdo {X} while (0)はIMOであり、関数のように感じ、機能するマクロボディを作成する最良の方法です。ほとんどのコンパイラはとにかくループを最適化します。
Mike Clark、

7

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

ポインタをNULLに設定した場合、それにアクセスすると、プログラムは常にsegfaultでクラッシュします。予測できない方法でクラッシュすることはありません。デバッグがはるかに簡単です。


5
プログラムは常にsegfaultでクラッシュするとは限りません。ポインターにアクセスする方法が、逆参照の前に十分に大きなオフセットが適用されていることを意味する場合、アドレス指定可能なメモリに到達する可能性があります:((MyHugeStruct *)0)-> fieldNearTheEnd。そして、それは、アクセス0でセグメンテーション違反を起こさないハードウェアを扱う前でもあります。ただし、プログラムはsegfaultでクラッシュする可能性が高くなります。
スティーブジェソップ

7

ポインターをfree'dメモリーに設定すると、ポインターを介してそのメモリーにアクセスしようとすると、未定義の動作が発生する代わりに、すぐにクラッシュします。これにより、問題が発生した場所を簡単に特定できます。

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

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


7

cの最も一般的なバグは、二重解放です。基本的にあなたはそのようなことをします

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

そしてそれは結局かなり悪いことになります、OSはすでに解放されたメモリを解放しようとし、一般的にそれはsegfaultです。したがって、に設定するNULLことをお勧めします。テストを行い、このメモリを本当に解放する必要があるかどうかを確認できます。

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

また、free(NULL)何も実行しないので、ifステートメントを記述する必要はありません。私は実際にはOSの第一人者ではありませんが、今でもほとんどのOSは二重解放でクラッシュします。

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


11
実際には、チェックせずにfree()を呼び出すだけでかまいません。free(NULL)は何もしないものとして定義されています。
アンバー

5
それはバグを隠さないのですか?(解放しすぎるように。)
GeorgSchölly2009

1
ありがとう、わかりました。私が試した:p = (char *)malloc(.....); free(p); if(p!=null) //p!=null is true, p is not null although freed { free(p); //Note: checking doesnot prevent error here }
Shaobo Wang

5
言ったように、渡されたポインタの値を変更するfree(void *ptr) ことはできません。これは変更することができるコンテンツポインタの、そのアドレスに格納されたデータではなく、アドレス自体、またはポインタの値。それにはfree(void **ptr)(明らかに標準では許可されていない)またはマクロ(許可されており、完全に移植可能ですが、マクロは好きではありません)が必要です。また、Cは利便性ではなく、プログラマが望むだけの制御をプログラマに与えることです。ポインタをに設定するオーバーヘッドの増加を望まない場合はNULL、強制するべきではありません。
クリス・ルッツ

2
Cコードの作成者の一部にプロ意識の欠如を放棄するものは、世界にはほとんどありません。ただし、「呼び出し前にポインタのNULLを確認するfree」(「メモリ割り当て関数の結果をキャストする」、「型名を「で考え抜いて使うsizeof」など)も含まれます。
AnT、2009

6

この背後にある考え方は、解放されたポインタの誤った再利用を停止することです。


4

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

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


Windowsでは、少なくともデバッグビルドはメモリを0xddddddddに設定するため、削除されたメモリへのポインタを使用するとすぐにわかります。すべてのプラットフォームに同様のメカニズムがあるはずです。
i_am_jorf 2009年

2
jeffamaphone、削除されたメモリブロックは、再度ポインタを使用するまでに再割り当てされ、別のオブジェクトに割り当てられている場合があります。
コンスタンティン

4

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;
}

@DrPizza-(私の意見では)エラーは、プログラムが想定どおりに機能しない原因となるものです。隠されたダブルフリーがプログラムを破壊する場合、それはエラーです。意図したとおりに機能する場合は、エラーではありません。
Chris Lutz、

@DrPizza:NULLマスキングエラーを回避するために設定する必要がある理由を見つけました。stackoverflow.com/questions/1025589/…どちらの場合も、いくつかのエラーが非表示になるようです。
GeorgSchölly、2009

1
voidポインターツーポインターには問題があることに注意してください:c-faq.com/ptrs/genericpp.html
安全な

3
@Chris、いいえ、最善のアプローチはコード構造です。コードベース全体でランダムなmallocとfreeをスローしないで、関連するものをまとめます。リソース(メモリ、ファイル、...)を割り当てる「モジュール」は、リソースを解放する責任があり、ポインタを管理するための関数も提供する必要があります。特定のリソースについては、割り当てられた場所と解放された場所が1つずつあり、両方が近接しています。
セキュリティで保護する

4
@Chris Lutz:ホグウォッシュ。同じポインターを2回解放するコードを作成すると、プログラムに論理エラーが発生します。クラッシュしないようにしてその論理エラーをマスキングしても、プログラムが正しいことを意味するわけではありません。ダブルフリーを書くことが正当化されるシナリオはありません。
DrPizza 09

4

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

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

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

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

要約すると、はい、私はしばしば何かを削除した後にポインタをNULLに設定しますが、それは大規模な設計の一部としてであり、コーディング標準ルールに盲目的に従うためではなく、データを所有する人についての考えです。私はあなたの例ではそうしません。そうすることには利点がなく、私の経験では、この種のものと同じくらいバグと悪いコードの原因である「乱雑」を追加するからです。


4

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

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

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

だからいつも行く

free(ptr);
ptr = NULL;

3

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

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

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

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

これをNULLに設定すると、実際には後で論理エラーがマスクされる可能性があると主張するかもしれません。

一般的に、それが良いアイデアだと思うときはNULLに設定し、価値がないと思うときは気にしないことをお勧めします。代わりに、短い関数と適切に設計されたクラスを書くことに焦点を当ててください。


2

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


if(ptr)
   ptr->CallSomeMethod();

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


5
多くの場合、NULLポインターが意味をなさない場合は、代わりにアサーションを作成することをお勧めします。
Erich Kitzmueller、2009年

2

これはすべてのポインタを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、関係のないあらゆる種類のものが上書きされ、醜いバグにつながる可能性があります。特に、コンパイラがデバッグモードでローカル変数をゼロで初期化したが、最適化をオンにしていない場合。したがって、デバッグビルドはバグの兆候を示さず、リリースビルドはランダムに爆発します...


2

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


2

NULLへのポインターの設定は、いわゆるダブルフリーを再度保護することです。そのアドレスでブロックを再割り当てせずに、同じアドレスに対してfree()が複数回呼び出される状況です。

ダブルフリーは未定義の動作につながります-通常、ヒープの破損またはプログラムの即時クラッシュ。NULLポインタに対してfree()を呼び出しても何も行われないため、安全であることが保証されています。

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


2

アイデアは、有効期限が切れたポインターを解放した後で逆参照しようとすると、黙って不思議にではなく、ハードに失敗したい(segfault)ことです。

しかし...注意してください。NULLを間接参照すると、すべてのシステムでsegfaultが発生するわけではありません。(少なくともいくつかのバージョンの)AIXでは、*(int *)0 == 0であり、SolarisはこのAIXの「機能」とオプションで互換性があります。


2

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

どのシステムでも、正しいことを最も簡単にするという達成不可能な目標があり、不正確な測定の削減できないコストがあります。Cでは、熟練労働者の手に多くのことを生み出し、不適切に取り扱われるとあらゆる種類の隠喩的な損傷を与える可能性のある、非常に鋭い、非常に強力なツールのセットを提供しています。一部は正しく理解または使用するのが難しいです。そして、人々は自然にリスクを嫌うので、ポインタをNULL値でチェックしてからそれを使ってfreeを呼び出すなど、不合理なことをしています…

測定の問題は、良いものと悪いものを分けようとするときはいつでも、ケースが複雑になるほど、あいまいな測定値が得られる可能性が高くなることです。目標が良い実践のみを維持することである場合、いくつかのあいまいなものは、実際には良くないものを捨てられます。目標が善ではないものを排除することである場合、あいまいさは善にとどまることがあります。良好な状態を維持するか、明らかに悪い状態を排除するという2つの目標は正反対ですが、通常、3つ目のグループはどちらか一方ではなく、両方とも存在します。

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


いい答えだ。1つ追加したいのですが。バグデータベースを確認することは、さまざまな理由で役立ちます。ただし、元の質問のコンテキストでは、無効なポインターの問題がいくつ防止されたか、または少なくともバグデータベースに登録されていないほど早く検出されたかを知るのは難しいことに注意してください。バグ履歴は、コーディング規則を追加するためのより良い証拠を提供します。
ジムハーク2018

2

2つの理由があります。

二重解放時のクラッシュを回避

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

cの最も一般的なバグは、二重解放です。基本的にあなたはそのようなことをします

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

そしてそれは結局かなり悪いことになります、OSはすでに解放されたメモリを解放しようとし、一般的にそれはsegfaultです。したがって、に設定するNULLことをお勧めします。テストを行い、このメモリを本当に解放する必要があるかどうかを確認できます。

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

また、free(NULL) 何も実行しないので、ifステートメントを記述する必要はありません。私は実際にはOSの第一人者ではありませんが、今でもほとんどのOSは二重解放でクラッシュします。

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

すでに解放されたポインターの使用を避ける

書かれたマーティン・V。LOWIS別の答え

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

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

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


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要件を満たすことができます。


1

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

int *ptr = NULL;

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

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.

1

簡単に言うと、解放したアドレスに誤って(誤って)アクセスしたくありません。アドレスを解放すると、ヒープ内のそのアドレスを他のアプリケーションに割り当てることができるからです。

ただし、ポインターをNULLに設定せず、誤ってポインターの逆参照を試みるか、そのアドレスの値を変更してください。あなたはまだそれを行うことができます。ただし、論理的に実行する必要があるものではありません。

解放したメモリ位置にまだアクセスできるのはなぜですか?理由:メモリを解放している可能性がありますが、ポインタ変数にはヒープメモリアドレスに関する情報が残っています。したがって、防御戦略として、NULLに設定してください。

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