0

seg-Vで次のクラッシュが発生します。

// my code
int* ipt;
int bool set = false;
void Set(int* i) {
  ASSERT(i);
  ipt = i;
  set = true;
}

int Get() {
  return set ? *ipt : 0;
}

// code that I don't control.
struct S { int I, int J; }
int main() {
  S* ip = NULL;
  // code that, as a bug, forgets to set ip...
  Set(&ip->J);
  // gobs of code
  return Get();
}

これは、そうでiはないがNULL、それでも有効ではないためです。NULL呼び出し元のコードがポインタから配列インデックス操作のアドレスを取得する場合にも、同じ問題が発生する可能性があります。

これに対する1つの解決策は、下位ビットをトリミングすることです。

void Set(int* i) {
  ASSERT((reinterpret_cast<size_t>(i))>>10);
  ipt = i;
  set = true;
}

しかし、いくつのビットを取り除く必要がありますか?


編集、とにかくその場合は中止するので(ただし、seg-vよりもクリーンに)、未定義の動作については心配していません。

FWIW:これは半仮想的な状況です。これを考えさせられたバグは、投稿する前に修正されましたが、以前に遭遇したことがあり、将来どのように処理するかを考えています。

議論のために想定できること:

  • Setがseg-vになるもので呼び出された場合、それはバグです
  • Setは、修正するのが私の仕事ではないコードによって呼び出される可能性があります。(たとえば、バグを報告します)
  • Setは、修正しようとしているコードによって呼び出される可能性があります。(たとえば、デバッグ作業の一部として健全性チェックを追加しています。)
  • Setが呼び出された場所に関する情報を提供しない方法で呼び出されます。(つまり、Get to seg-vを許可することは、何かをデバッグするための効果的な方法ではありません。)
  • コードは移植可能である必要はなく、不正なポインターを100%キャッチする必要もありません。現在のシステムで十分な頻度で動作するだけで、どこで問題が発生しているのかを見つけることができます。
4

8 に答える 8

12

NULL以外の無効なポインタをテストする移植可能な方法はありません。評価&ip[3]すると、何かをする前に、未定義の動作が発生します。唯一の解決策は、ポインタで算術演算を行うにNULLをテストすることです。

移植性が不要で、すべてのエラーを確実にキャッチする必要がない場合は、ほとんどの主流プラットフォームで、アドレスがメモリの最初のページ内にあるかどうかを確認できます。NULLをアドレスゼロとして定義し、最初のページを予約してほとんどのnullポインター逆参照をトラップするのが一般的です。POSIXプラットフォームでは、これは次のようになります。

static size_t page_size = sysconf(_SC_PAGESIZE);
assert(reinterpret_cast<intptr_t>(i) >= page_size);

しかし、これは完全な解決策ではありません。唯一の本当の解決策は、そもそもnullポインタを悪用しているものを修正することです。

于 2010-09-15T17:38:18.857 に答える
2

nullポインタからポインタ演算(配列のインデックス付けを含む)を行うべきではありません。

また、C ++では0なく、を使用する必要があります。NULLNULLはcの機能であり、引き続きサポートされていますが、c++では慣用的ではありません。


BCSの多くのコメントと編集に関して。これにより、問題は表面的な単純なものからはるかに深いものに変わります。しかし...コードを呼び出す前に愚かなことをする人々から身を守ることは、c++のように寛容な言語では簡単ではありません。

于 2010-09-15T17:40:28.807 に答える
1

未定義の動作を回避しようとすると、常にプラットフォーム、コンパイラ、バージョンなどに大きく依存します。可能であれば。

一般的な*nixesは、アドレス空間の最初のページを正確にマップしてnullポインターアクセスをキャッチすることはありません。したがって、ポインター値が0〜4096(またはシステムが使用するページサイズ)であるかどうかを確認する必要がない場合があります。

ただし、これを行わないでください。問題が発生する可能性のあるすべてのものを防ぐことはできません。代わりに、コードを正しく取得することに集中してください。誰かがあなたに無効なポインターを渡した場合、ポインター検証チェックで修正できない重大な問題がある可能性があります。

于 2010-09-15T17:47:09.647 に答える
1

その悪いコードを修正するために何らかの影響を与えることができる方法はありますか?これがうまくいく方法はありません。法的に、無効なポインタを作成するだけでは未定義の動作です。

Setから常に小さなオフセットが渡され、ip常にNULLに初期化される場合ipは、実行していることで問題がない可能性があります。最近のほとんどのシステムでは、すべてのビットがゼロであるため、nullポインター定数があり、ほとんどのシステムは自然なことを行います。もちろん、特定のコンパイラと特定のコンパイラオプションを備えた特定のシステムで動作するという保証はまったくありません。これらのいずれかを変更すると、失敗する可能性があります。

不正なポインタを使用するとプログラムが失敗する可能性があるため、コードがメモリ違反をトリガーしたときに何が起こるかを考慮する必要があります。

ASSERTまた、マクロが何をするのかわかりませんがassert、ほとんどの実装では、デバッグモードでのみアクティブ化されます。このジャンクを本番環境にプッシュする場合、または最適化モードで実行する場合は、それでもより穏やかに失敗することを確認することをお勧めします。

于 2010-09-15T18:26:28.760 に答える
1

本当に悪いハックを気にしないのであれば、でメモリアクセスを強制することができますvolatile(nbvolatileは悪です)。GCCのドキュメントによると、揮発性アクセスはシーケンスポイント間で順序付けする必要があるため、次のように実行できます。

int test = *(volatile int *)i;
*(volatile int *)i = test;

=シーケンスポイントではないと思いますが、次のことも機能する可能性があります。

*(volatile int *)i = *(volatile int *)i;
于 2010-09-15T18:31:14.760 に答える
1

他の誰かのコードのバグを回避しようとすることは本当にお勧めしません。コードの開発中にデバッガーを介して記述したすべてを実行していない場合、すべての問題を検出するのに役立つチェックはありません。彼らに彼らのコードを修正してもらいます。

デバッガーを使用していない場合は、各スレッドのコールスタックとプログラムの状態に関する可能な限り多くの追加情報をダンプする適切なクラッシュハンドラーを入手してください。それから何がうまくいかないのかを理解してみてください。

静的分析ツールを使用してコードを定期的に実行することも、ここで役立ちます。

ポインタの初期化を忘れている人ではない可能性があることを忘れないでください。まったく関係のない場所からの不正なメモリ書き込みによって、他の誰かがそのポインタを上書きしている可能性があります。そのようなことを追跡するのに役立つツールもあります。

NULL対0の議論に関しては#define NULL 0、いくつかの理由でより良いです:

1)ポインタを扱っているときがより簡単にわかります。

2)NULLを使用すると、0を使用する場合と同じかそれ以上の安全性が得られます。では、コードを読みやすくしてみませんか?

3)C ++ 11が最終的にリリースされたとき#define NULL nullptr、これらすべてのゼロよりも変更がはるかに簡単です。(逆の方向に進むこともできますが、#define nullptr 0今日はそう思いますが、クロスプラットフォームのコードを開発している場合は、将来的に問題が発生する可能性があります。)

そして、記録のために、C ++標準は、nullポインター定数がゼロと評価される右辺値整数型であると明示的に述べています。したがって、nullポインタがゼロに等しくなくてもよいことについてこれ以上ナンセンスにならないようにしてください。

于 2010-09-15T18:41:13.900 に答える
0

多くの場合、これを移植可能な方法で実行できない理由の1つは、NULLが0であることが保証されていないことです。nullポインターが0と等しくなるように指定されているだけです。0(またはプリプロセッサーマクロ「NULL」)を記述できます。 )コード内にありますが、コンパイラはこの0がポインタコンテキストにあることを認識しているため、nullポインタの実際の実装が何であれ、nullポインタと比較するための適切なコードを生成します。詳細については、ここここを参照してください。NULLポインターを整数型として再解釈すると、falseではなくtrueの値になる可能性があります。

于 2010-09-15T17:55:59.673 に答える
0

特定のオペレーティングシステムとハードウェアアーキテクチャを考慮する必要があります。「nullに近い」ポインタの検出のみに関心がある場合は、OSで最初のページが常に書き込み保護されていると仮定して、ASSERT(i> pageSize)を使用できます。

しかし...明らかな質問は次のとおりです。なぜわざわざ?この場合、OSはnullを検出し、ご指摘のとおりSEGVを検出します。これは、ASSERTと同じくらい優れていますね。

于 2010-09-15T18:51:43.330 に答える