ポインターの有効性のテスト(C / C ++)


90

与えられたポインタが「有効」かどうかを(プログラム的にはもちろん)判断する方法はありますか?NULLのチェックは簡単ですが、0x00001234のようなものはどうですか?この種類のポインタを逆参照しようとすると、例外/クラッシュが発生します。

クロスプラットフォームの方法が推奨されますが、プラットフォーム固有(WindowsおよびLinuxの場合)でも問題ありません。

明確化のための更新: 問題は、古くなった、解放された、初期化されていないポインターには関係ありません。代わりに、呼び出し元からポインターを取得するAPI(文字列へのポインター、ファイルハンドルなど)を実装しています。呼び出し元は、ポインタとして(意図的または誤って)無効な値を送信できます。クラッシュを防ぐにはどうすればよいですか?



Linuxに対する最良の肯定的な答えはGeorge Carretteによって与えられたと思います。それでも不十分な場合は、関数シンボルテーブルをライブラリに構築するか、独自の関数テーブルを備えた利用可能なライブラリの別のレベルのテーブルを構築することを検討してください。次に、それらの正確なテーブルと照合します。もちろん、これらの否定的な答えも正しいです。ユーザーアプリケーションに多くの追加の制限を課さない限り、関数ポインターが有効かどうかを100%確信することはできません。
minghua 2015年

API仕様では、実装によって満たす義務を実際に指定していますか?ちなみに、あなたはあなたが開発者であり設計者でもあるとは想定されていなかったと思います。私の要点は、APIが「無効なポインターが引数として渡された場合、関数は問題を処理してNULLを返す必要がある」のようなものを指定することはないと思います。APIは、ハッキングではなく、適切な使用条件下でサービスを提供する義務を負います。それにもかかわらず、それは少し愚かな証拠であることは害を及ぼしません。リファレンスを使用することで、そのようなケースの混乱が広がりません。:)
Poniros

回答:


75

明確化のための更新:問題は、古くなった、解放された、または初期化されていないポインタにはありません。代わりに、呼び出し元からポインターを取得するAPI(文字列へのポインター、ファイルハンドルなど)を実装しています。呼び出し元は、ポインタとして(意図的または誤って)無効な値を送信できます。クラッシュを防ぐにはどうすればよいですか?

あなたはそのチェックをすることはできません。ポインタが「有効」かどうかを確認する方法はありません。あなたは人々がポインタを取る関数を使うとき、それらの人々は彼らが何をしているのか知っていると信頼しなければなりません。彼らがポインタ値として0x4211を渡す場合は、アドレス0x4211を指していると信頼する必要があります。そして、「偶然に」オブジェクトにヒットした場合、恐ろしい操作システム関数(IsValidPtrなど)を使用しても、バグに陥り、すぐに失敗することはありません。

この種のことを知らせるためにnullポインターの使用を開始し、誤って無効なポインターを誤って渡してしまう可能性がある場合は、ポインターを使用しないようにライブラリーのユーザーに伝えます:)


これはおそらく正しい答えですが、一般的なhexspeakメモリの場所をチェックする単純な関数は、一般的なデバッグに役立つと思います...現在、時々0xfeeefeeeを指すポインターがあり、単純な関数があれば、唐辛子の使用は、それがはるかに簡単に犯人... EDITを見つけることになるだろう周りを主張する:私が推測1を自分で書くことは難しいことではないでしょうが...
定量

@quant問題は、一部のCおよびC ++コードが(ガベージイン、ガベージアウトの原則に基づいて)チェックせずに無効なアドレスでポインター演算を実行できるため、これらのウェルのいずれかから「算術的に変更された」ポインターを渡すことです。 -既知の無効なアドレス。一般的なケースは、無効なオブジェクトアドレスまたは間違った型の1つに基づいて、存在しないvtableからメソッドを検索する場合、またはポインターを指し示さない構造体へのポインターからフィールドを単に読み取る場合です。
rwong

これは基本的に、外の世界からのみ配列インデックスを取得できることを意味します。呼び出し元から自分自身を守る必要があるAPIは、インターフェースにポインターを含めることはできません。ただし、ポインターの有効性に関するアサーション(内部でバインドする必要がある)で使用するマクロがあるとなお良いでしょう。ポインタが、開始点と長さがわかっている配列内を指すことが保証されている場合は、明示的に確認できます。deref(文書化されていないエラー)よりもassert違反(文書化されたエラー)で死ぬ方が良いです。
Rob

34

LinuxでのCプログラムが実行されているメモリのステータスを内省する3つの簡単な方法と、一部のコンテキストで質問に適切で高度な回答がある理由を以下に示します。

  1. getpagesize()を呼び出し、ポインターをページ境界に丸めた後、mincore()を呼び出して、ページが有効かどうか、およびページがプロセスのワーキングセットの一部であるかどうかを確認できます。これにはいくつかのカーネルリソースが必要なので、ベンチマークして、この関数の呼び出しがAPIで本当に適切かどうかを判断する必要があります。APIが割り込みを処理する場合、またはシリアルポートからメモリに読み取る場合は、これを呼び出して予測できない動作を回避することが適切です。
  2. stat()を呼び出して/ proc / selfディレクトリーが使用可能かどうかを判別した後、fopenを実行して/ proc / self / mapsを読み取り、ポインターが存在する領域に関する情報を見つけることができます。プロセス情報の疑似ファイルシステムであるprocのmanページを調べてください。明らかにこれは比較的高価ですが、解析の結果を配列にキャッシュして、バイナリ検索を使用して効率的に検索できるようにすることができる場合があります。/ proc / self / smapsも検討してください。APIがハイパフォーマンスコンピューティング向けである場合、プログラムは/ proc / self / numaについて知る必要があります。これは、非均一メモリアーキテクチャであるnumaのマニュアルページに記載されています。
  3. get_mempolicy(MPOL_F_ADDR)呼び出しは、複数の実行スレッドがあり、CPUコアとソケットリソースに関連する非均一メモリとの親和性を持つように作業を管理しているハイパフォーマンスコンピューティングAPI作業に適しています。もちろん、そのようなAPIは、ポインターが有効かどうかも通知します。

Microsoft Windowsには、Process Status API(NUMA APIでも)に記載されているQueryWorkingSetEx関数があります。洗練されたNUMA APIプログラミングの当然の結果として、この関数を使用すると、単純な「ポインターの有効性のテスト(C / C ++)」作業を行うことができます。そのため、少なくとも15年間は非推奨にはなりません。


12
質問自体について道徳的になることを試みず、実際には完全に答える最初の答え。サードパーティのライブラリやレガシーコードなどのバグを見つけるために、このようなデバッグ方法が本当に必要なことに気付かない人もいます。valgrindでも、実際にアクセスするときにワイルドポインターしか見つからないためです。コードの他の場所から上書きされたキャッシュテーブル内
lumpidu

これは受け入れられる答えになるはずです。Linux以外のプラットフォームでも同様に行いました。基本的には、プロセス情報をプロセス自体に公開します。この側面では、WindowsはプロセスステータスAPIを介してより意味のある情報を公開することにより、Linuxよりもうまく機能しているように見えます。
minghua 2015年

31

呼び出し元が無効なポインタを送信することで発生するクラッシュを防ぐことは、見つけにくいサイレントバグを作成するための良い方法です。

APIを使用するプログラマーにとって、コードを隠すのではなく、クラッシュさせることにより、コードが偽物であるという明確なメッセージを取得する方がいいのではないでしょうか。


8
しかしいくつかのケースでは、APIが呼び出された直後に悪いポインタをチェックすること、あなたが早期に失敗しますか。たとえば、APIがポインターをデータ構造に格納し、後で参照されるだけの場合はどうなりますか?次に、APIに不正なポインタを渡すと、後でランダムにクラッシュします。その場合、悪い値が最初に導入されたAPI呼び出しで、より早く失敗する方が良いでしょう。
peterflynn 2015

28

Win32 / 64では、これを行う方法があります。ポインタを読み取って、失敗した場合にスローされる結果のSEH例外をキャッチしようとします。スローしない場合は、有効なポインタです。

ただし、このメソッドの問題は、ポインターからデータを読み取ることができるかどうかを返すだけです。型の安全性やその他の不変条件については保証しません。一般に、この方法は、「はい、私は現在経過したときに、メモリ内のその特定の場所を読み取ることができます」と言う以外にほとんど役に立ちません。

つまり、これを行わないでください;)

Raymond Chenがこの件についてブログ投稿しています:http : //blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx


3
@ Tim、C ++でそれを行う方法はありません。
JaredPar

6
「有効なポインタ」を「アクセス違反/ segfaultを引き起こさない」と定義した場合、それは「正しい答え」に過ぎません。「あなたが使用する目的のために割り当てられた意味のあるデータへのポイント」としてそれを定義したいと思います。私はそれがポインタの有効性のより良い定義だと主張します...;)
jalf

ポインタが有効であっても、この方法ではチェックできません。thread1(){.. if(IsValidPtr(p))* p = 7;と考えてください。...} thread2(){sleep(1); pを削除します。...}
クリストファー

2
@クリストファー、とても本当です。私は「今、過ぎ去った時のメモリ内のその特定の場所を読むことができる」と言っておくべきだった
JaredPar

@JaredPar:本当に悪い提案。ガードページをトリガーできるため、スタックが後で拡張されることも、同じように拡張されることもありません。
デデュプリケータ2014

16

私の知る限り、方法はありません。メモリを解放した後は常にポインタをNULLに設定して、この状況を回避するようにしてください。


4
ポインターをnullに設定しても、誤った安心感以外は何も起こりません。

それは真実ではありません。特にC ++では、nullをチェックすることでメンバーオブジェクトを削除するかどうかを決定できます。また、C ++ではnullポインタを削除することが有効であるため、デストラクタ内のオブジェクトを無条件に削除することが一般的です。
フェルディナンドベイヤー

4
int * p = new int(0); int * p2 = p; pを削除します。p = NULL; p2を削除します。//クラッシュ

1
ザブゾンク、そして?? 彼が言ったことは、nullポインタを削除できるということです。p2はNULLポインターではありませんが、無効なポインターです。前にnullに設定する必要があります。
ヨハネスシャウブ-litb 2009

2
指すメモリのエイリアスがある場合、そのうちの1つだけがNULLに設定され、他のエイリアスはぶらぶらしています。
jdehaan


7

このスレッドで答えを少し上げます:

IsBadReadPtr()、IsBadWritePtr()、IsBadCodePtr()、IsBadStringPtr()for Windows。

私のアドバイスはそれらから離れることです、誰かがすでにこれを投稿しています:http : //blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx

同じトピックに関する同じ著者の別の投稿(私は思う)はこれです:http : //blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx( "IsBadXxxPtrは本当にCrashProgramRandomlyと呼ばれるべきです")。

APIのユーザーが不正なデータを送信すると、クラッシュします。渡されたデータが後で使用されないことが問題である場合(そのため、原因の特定が難しくなります)、エントリに文字列などが記録されるデバッグモードを追加します。それらが悪い場合、それは明白です(そしておそらくクラッシュします)。頻繁に発生している場合は、APIをプロセス外に移動して、メインプロセスではなくAPIプロセスをクラッシュさせることをお勧めします。


おそらく別の方法は_CrtIsValidHeapPointerを使用することです。この関数は、ポインターが有効な場合にTRUEを返し、ポインターが解放されたときに例外をスローします。記載されているように、この関数はデバッグCRTでのみ使用できます。
Crend King

6

まず、意図的にクラッシュを引き起こそうとする呼び出し元から自分自身を保護しようとする意味がありません。無効なポインタを介してアクセスしようとすることで、これを簡単に行うことができます。他にも多くの方法があります-それらはあなたのメモリやスタックを上書きするだけです。この種のことから保護する必要がある場合は、ソケットまたは通信用の他のIPCを使用して、別のプロセスで実行する必要があります。

私たちは、パートナー/顧客/ユーザーが機能を拡張できるようにするソフトウェアをかなり多く書いています。必然的にすべてのバグが最初に報告されるため、問題がプラグインコードにあることを簡単に示すことができると便利です。さらに、セキュリティ上の懸念があり、一部のユーザーは他のユーザーよりも信頼されています。

パフォーマンス/スループットの要件と信頼性に応じて、さまざまな方法を使用します。最も好ましいものから:

  • ソケットを使用してプロセスを分離します(多くの場合、データをテキストとして渡します)。

  • 共有メモリを使用する個別のプロセス(大量のデータを渡す場合)。

  • 同じプロセスがメッセージキューを介してスレッドを分離します(短いメッセージが頻繁に発生する場合)。

  • 同じプロセスが別のスレッドすべてがメモリプールから割り当てられたデータを渡しました。

  • ダイレクトプロシージャコールによる同じプロセス-渡されたすべてのデータはメモリプールから割り当てられます。

特にサードパーティのソフトウェアを扱うときに、ソースコードではなくバイナリとしてプラグイン/ライブラリが提供されている場合は、ユーザーがしようとしていることに決して頼らないようにします。

メモリプールの使用はほとんどの状況で非常に簡単であり、非効率的である必要はありません。最初にデータを割り当てた場合は、割り当てた値に対してポインターをチェックするのは簡単です。割り当てられた長さを保存し、データの前後に「マジック」値を追加して、有効なデータタイプとデータオーバーランをチェックすることもできます。


4

私は私とほとんど同じ立場にいるので、私はあなたの質問に多くの同情を持っています。私は多くの返信が言っていることに感謝します、そしてそれらは正しいです-ポインター提供するルーチンは有効なポインターを提供するべきです。私の場合、彼らがポインタを破壊した可能性はほとんど考えられません-しかし、彼ら管理してたら、MYソフトウェアがクラッシュし、MEが責任を負います:-(

私の要件は、セグメンテーション違反の後で続行することではありません-それは危険です-私を非難するのではなく、コードを修正できるように、終了する前に顧客に何が起こったかを報告したいだけです!

これは私がそれを行うことがわかった方法です(Windowsの場合):http : //www.cplusplus.com/reference/clibrary/csignal/signal/

概要を示すには:

#include <signal.h>

using namespace std;

void terminate(int param)
/// Function executed if a segmentation fault is encountered during the cast to an instance.
{
  cerr << "\nThe function received a corrupted reference - please check the user-supplied  dll.\n";
  cerr << "Terminating program...\n";
  exit(1);
}

...
void MyFunction()
{
    void (*previous_sigsegv_function)(int);
    previous_sigsegv_function = signal(SIGSEGV, terminate);

    <-- insert risky stuff here -->

    signal(SIGSEGV, previous_sigsegv_function);
}

これで、期待どおりに動作するように見えます(エラーメッセージが出力され、プログラムが終了します)。しかし、誰かが欠陥を見つけた場合は、お知らせください!


は使用しないでくださいexit()。RAIIを回避するため、リソースリークが発生する可能性があります。
Sebastian Mach、2012年

興味深い-この状況できれいに終了する別の方法はありますか?そして、exitステートメントは、このようにすることの唯一の問題ですか?「-1」を取得したことに気づきました-それは単に「出口」のせいですか?
Mike Sadler

おっと、これはかなり例外的な状況であることがわかりました。見たところexit()、私のポータブルC ++アラームベルが鳴り始めました。このLinux固有の状況では問題ありませんが、プログラムはとにかく終了し、ノイズが発生します。
セバスチャンマッハ

1
signal(2)は移植可能ではありません。sigaction(2)を使用してください。 man 2 signalLinuxでは理由を説明する段落があります。
rptb1

1
この状況では、通常、exit(3)ではなくabort(3)を呼び出します。これは、死後の問題を診断するために使用できるある種のデバッグバックトレースを生成する可能性が高いためです。ほとんどのUnixenでは、abort(3)はコアをダンプし(コアダンプが許可されている場合)、Windowsでは、インストールされている場合はデバッガを起動するように提案します。
rptb1

4

Unixでは、ポインタチェックを実行してEFAULTを返すカーネルsyscallを利用できるはずです。

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdbool.h>

bool isPointerBad( void * p )
{
   int fh = open( p, 0, 0 );
   int e = errno;

   if ( -1 == fh && e == EFAULT )
   {
      printf( "bad pointer: %p\n", p );
      return true;
   }
   else if ( fh != -1 )
   {
      close( fh );
   }

   printf( "good pointer: %p\n", p );
   return false;
}

int main()
{
   int good = 4;
   isPointerBad( (void *)3 );
   isPointerBad( &good );
   isPointerBad( "/tmp/blah" );

   return 0;
}

戻る:

bad pointer: 0x3
good pointer: 0x7fff375fd49c
good pointer: 0x400793

これは、実際のファイル作成コードパスとそれに続くクローズ要件につながる可能性があるため、おそらくopen()[おそらくアクセス]よりも使用するより良いsyscallがあります。


これは素晴らしいハックです。特に副作用がないことが保証できる場合は、メモリ範囲を検証するためのさまざまなsyscallに関するアドバイスを参照してください。ファイル記述子を開いたままにして/ dev / nullに書き込み、バッファーが読み取り可能なメモリにあるかどうかをテストすることもできますが、おそらくもっと簡単な解決策があります。私が見つけることができる最高のものは、symlink(ptr、 "")です。これにより、errnoが不良アドレスで14または正常アドレスで2に設定されますが、カーネルの変更により検証の順序が入れ替わることがあります。
プレストン

@Preston DB2では、以前はunistd.hのaccess()を使用していたと思います。上記のopen()を使用したのは、少しあいまいさが少ないためですが、使用できるシステムコールがたくさんあることはおそらく正しいでしょう。Windowsには明示的なポインターチェックAPIがありましたが、スレッドセーフではないことが判明しました(SEHを使用して、メモリ範囲の境界を書き込んでから復元しようとしたと思います。)
Peeter Joot

2

C ++には、一般的なケースとしてポインターの有効性をテストするための規定はありません。明らかにNULL(0x00000000)は悪いと想定できます。さまざまなコンパイラやライブラリは、あちこちに「特別な値」を使用してデバッグを容易にします(たとえば、ビジュアルスタジオでポインタが0xCECECECEとして表示される場合、私は知っています。私は何か間違ったことをしました)しかし、真実はポインターがメモリへの単なるインデックスであるため、それが「正しい」インデックスであるかどうかをポインターを見ただけではほとんど不可能です。

dynamic_castとRTTIを使用して、ポイントされたオブジェクトが目的のタイプであることを確認するためにできるさまざまなトリックがありますが、それらはすべて、そもそも有効なものをポイントする必要があります。

プログラムが「無効な」ポインタを確実に検出できるようにしたい場合、私のアドバイスは次のとおりです。宣言するすべてのポインタを作成直後にNULLまたは有効なアドレスに設定し、指すメモリを解放した直後にNULLに設定します。この慣行に熱心である場合は、NULLを確認するだけで十分です。


C ++(または、C)のnullポインター定数は、定数の整数ゼロで表されます。多くの実装では、すべて2進ゼロを使用してそれを表現していますが、これは当てになりません。
David Thornley、

2

これを行うためのポータブルな方法はなく、特定のプラットフォームでそれを行うことは困難と不可能の間のどこにでもあり得ます。いずれにせよ、そのようなチェックに依存するコードを記述してはいけません。そもそもポインタが無効な値をとらないようにしてください。


2

使用の前後にポインタをNULLに設定することは良いテクニックです。たとえば、クラス(文字列)内でポインターを管理する場合、これはC ++で簡単に実行できます。

class SomeClass
{
public:
    SomeClass();
    ~SomeClass();

    void SetText( const char *text);
    char *GetText() const { return MyText; }
    void Clear();

private:
    char * MyText;
};


SomeClass::SomeClass()
{
    MyText = NULL;
}


SomeClass::~SomeClass()
{
    Clear();
}

void SomeClass::Clear()
{
    if (MyText)
        free( MyText);

    MyText = NULL;
}



void SomeClass::Settext( const char *text)
{
    Clear();

    MyText = malloc( strlen(text));

    if (MyText)
        strcpy( MyText, text);
}

もちろん、更新された質問は私の回答を間違ったものにします(または少なくとも別の質問への回答)。私が基本的に言っている答えに同意します、彼らがth apiを乱用すればクラッシュさせてください。ハンマーで親指をたたく人を止めることはできません...
ティムリング

2

パブリックAPIで入力パラメーターとして任意のポインターを受け入れることは、あまり良い方針ではありません。整数、文字列、構造体などの「プレーンデータ」タイプを使用することをお勧めします(もちろん、プレーンデータを内部に持つ従来の構造体を意味します。正式には、構造体であれば何でも構いません)。

どうして?なぜなら、他の人が言うように、有効なポインタが与えられているのか、それともジャンクを指しているのかを知るための標準的な方法はないからです。

ただし、選択できない場合もあります。APIはポインターを受け入れる必要があります。

これらの場合、適切なポインタを渡すことは呼び出し側の義務です。NULLは値として受け入れられますが、ジャンクへのポインタではありません。

何らかの方法で再確認できますか?まあ、そのような場合に私がしたことは、ポインターが指す型の不変式を定義し、それを取得したときに(デバッグモードで)呼び出すことでした。少なくとも、不変条件が失敗(またはクラッシュ)した場合、不正な値が渡されたことがわかります。

// API that does not allow NULL
void PublicApiFunction1(Person* in_person)
{
  assert(in_person != NULL);
  assert(in_person->Invariant());

  // Actual code...
}

// API that allows NULL
void PublicApiFunction2(Person* in_person)
{
  assert(in_person == NULL || in_person->Invariant());

  // Actual code (must keep in mind that in_person may be NULL)
}

re: "文字列のようなプレーンなデータ型を渡す"しかし、C ++では文字列は文字(char *)または(const char *)へのポインターとして渡されることが多いため、ポインターを渡すことに戻ります。また、例ではポインターとしてではなく参照としてin_personを渡すため、比較(in_person!= NULL)は、Personクラスで定義されたオブジェクト/ポインターの比較があることを意味します。
ジェシーチザム

@JesseChisholm文字列とは、文字列、つまりstd :: stringを意味しました。文字列を格納したり、文字列を渡したりする方法としてchar *を使用することはお勧めしません。それをしないでください。
Daniel Daranas

@JesseChisholmなんらかの理由で、5年前にこの質問に回答したときに間違いを犯しました。明らかに、Person&がNULLかどうかを確認することは意味がありません。それもコンパイルされません。参照ではなく、ポインタを使用することを意味していました。今直しました。
Daniel Daranas 14

1

他の人が言ったように、無効なポインターを確実に検出することはできません。無効なポインターが取る可能性のある形式をいくつか考えてみます。

あなたはヌルポインタを持つことができました。これは、簡単に確認して何かを実行できるものです。

有効なメモリ以外の場所へのポインタを持つことができます。有効なメモリを構成するものは、システムのランタイム環境がアドレス空間をセットアップする方法によって異なります。Unixシステムでは、通常、0から始まり、数メガバイトに達する仮想アドレス空間です。組み込みシステムでは、かなり小さい場合があります。いずれの場合も、0から開始しない場合があります。アプリがスーパーバイザーモードまたは同等のモードで実行されている場合、ポインターは実際のアドレスを参照している可能性があり、実際のメモリでバックアップされている場合とされていない場合があります。

データセグメント、bss、スタック、またはヒープ内であっても、有効なメモリ内のどこかにポインタを置くことはできますが、有効なオブジェクトを指すことはできません。このバリアントは、オブジェクトに何か問題が発生する前に、有効なオブジェクトを指すために使用されていたポインタです。このコンテキストでの悪いことには、割り当て解除、メモリの破損、またはポインタの破損が含まれます。

参照されているものに対して不正なアラインメントを持つポインターなど、フラットアウトの不正なポインターがある可能性があります。

セグメント/オフセットベースのアーキテクチャやその他の奇数ポインタの実装を検討すると、問題はさらに悪化します。この種のことは通常、優れたコンパイラーと型の賢明な使用によって開発者から隠されていますが、ベールを突き破ってオペレーティングシステムとコンパイラーの開発者を打ち負かしたい場合は、できますが、一般的な方法は1つではありません。あなたが遭遇する可能性のあるすべての問題を処理することを行うために。

あなたができる最善のことは、クラッシュを許可し、いくつかの良い診断情報を出すことです。


再:「いくつかの良い診断情報を出力する」、問題があります。ポインタの有効性を確認できないため、わずらわしい情報は最小限です。「ここで例外が発生しました。」コールスタック全体はすばらしいですが、ほとんどのC ++ランタイムライブラリが提供するよりも優れたフレームワークが必要です。
ジェシーチザム


1

一般的に、それは不可能です。これは特に厄介なケースです:

struct Point2d {
    int x;
    int y;
};

struct Point3d {
    int x;
    int y;
    int z;
};

void dump(Point3 *p)
{
    printf("[%d %d %d]\n", p->x, p->y, p->z);
}

Point2d points[2] = { {0, 1}, {2, 3} };
Point3d *p3 = reinterpret_cast<Point3d *>(&points[0]);
dump(p3);

多くのプラットフォームでは、次のように出力されます。

[0 1 2]

ランタイムシステムにメモリのビットを誤って解釈させることになりますが、この場合、ビットはすべて意味があるため、クラッシュすることはありません。これは、言語(とCスタイルの多型を見ての設計の一部でありstruct inaddrinaddr_ininaddr_in6)、あなたは確実に任意のプラットフォーム上でそれを防ぐことはできません。


1

上記の記事でどれほど多くの誤解を招く情報を読むことができるかは信じられないほどです...

マイクロソフトのmsdnドキュメントでもIsBadPtrは禁止されていると主張されています。まあ-私はクラッシュするよりも動作するアプリケーションを好む。用語の作業が正しく機能していない場合でも(エンドユーザーがアプリケーションを続行できる限り)。

グーグルで私はWindowsのための有用な例を見つけていません-32ビットアプリの解決策を見つけました、

http://www.codeproject.com/script/Content/ViewAssociatedFile.aspx?rzp=%2FKB%2Fsystem%2Fdetect-driver%2F%2FDetectDriverSrc.zip&zep=DetectDriverSrc%2FDetectDriver%2Fsrc%2FdrvCppLib%2Frtti.cpp&obid=58895otidt2 = 2

64ビットアプリもサポートする必要があるため、このソリューションはうまくいきませんでした。

しかし、私はwineのソースコードを取得し、64ビットアプリでも機能する同様の種類のコードを作成しました-ここにコードを添付します。

#include <typeinfo.h>   

typedef void (*v_table_ptr)();   

typedef struct _cpp_object   
{   
    v_table_ptr*    vtable;   
} cpp_object;   



#ifndef _WIN64
typedef struct _rtti_object_locator
{
    unsigned int signature;
    int base_class_offset;
    unsigned int flags;
    const type_info *type_descriptor;
    //const rtti_object_hierarchy *type_hierarchy;
} rtti_object_locator;
#else

typedef struct
{
    unsigned int signature;
    int base_class_offset;
    unsigned int flags;
    unsigned int type_descriptor;
    unsigned int type_hierarchy;
    unsigned int object_locator;
} rtti_object_locator;  

#endif

/* Get type info from an object (internal) */  
static const rtti_object_locator* RTTI_GetObjectLocator(void* inptr)  
{   
    cpp_object* cppobj = (cpp_object*) inptr;  
    const rtti_object_locator* obj_locator = 0;   

    if (!IsBadReadPtr(cppobj, sizeof(void*)) &&   
        !IsBadReadPtr(cppobj->vtable - 1, sizeof(void*)) &&   
        !IsBadReadPtr((void*)cppobj->vtable[-1], sizeof(rtti_object_locator)))  
    {  
        obj_locator = (rtti_object_locator*) cppobj->vtable[-1];  
    }  

    return obj_locator;  
}  

次のコードは、ポインターが有効かどうかを検出できます。おそらく、いくつかのNULLチェックを追加する必要があります。

    CTest* t = new CTest();
    //t = (CTest*) 0;
    //t = (CTest*) 0x12345678;

    const rtti_object_locator* ptr = RTTI_GetObjectLocator(t);  

#ifdef _WIN64
    char *base = ptr->signature == 0 ? (char*)RtlPcToFileHeader((void*)ptr, (void**)&base) : (char*)ptr - ptr->object_locator;
    const type_info *td = (const type_info*)(base + ptr->type_descriptor);
#else
    const type_info *td = ptr->type_descriptor;
#endif
    const char* n =td->name();

これはポインタからクラス名を取得します-あなたのニーズには十分だと思います。

私がまだ心配していることの1つは、ポインターチェックのパフォーマンスです-上記のコードスニペットでは、すでに3〜4個のAPI呼び出しが行われています-時間が重要なアプリケーションにとってはやり過ぎかもしれません。

たとえば、C#/マネージドc ++呼び出しと比較して、誰かがポインターチェックのオーバーヘッドを測定できれば、すばらしいでしょう。


1

実際、特定の状況下で何かを行うことができます。たとえば、文字列ポインタ文字列が有効かどうかを確認する場合、write(fd、buf、szie)syscallを使用すると、魔法を実行するのに役立ちます:fdを一時ファイルのファイル記述子にするテスト用に作成したファイル、テスティングしている文字列を指すbuf、ポインターが無効な場合、write()は-1を返し、errnoがEFAULTに設定されます。これは、bufがアクセス可能なアドレス空間の外にあることを示します。


1

以下はWindowsで動作します(誰かが以前に提案しました):

 static void copy(void * target, const void* source, int size)
 {
     __try
     {
         CopyMemory(target, source, size);
     }
     __except(EXCEPTION_EXECUTE_HANDLER)
     {
         doSomething(--whatever--);
     }
 }

関数は、いくつかのクラスの静的、スタンドアロン、または静的メソッドでなければなりません。読み取り専用でテストするには、ローカルバッファーにデータをコピーします。内容を変更せずに書き込みをテストするには、それらを上書きします。最初/最後のアドレスのみをテストできます。ポインターが無効な場合、制御は 'doSomething'に渡され、次に括弧の外側に渡されます。CStringのようなデストラクタを必要とするものは使用しないでください。


1

Windowsでは、次のコードを使用します。

void * G_pPointer = NULL;
const char * G_szPointerName = NULL;
void CheckPointerIternal()
{
    char cTest = *((char *)G_pPointer);
}
bool CheckPointerIternalExt()
{
    bool bRet = false;

    __try
    {
        CheckPointerIternal();
        bRet = true;
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
    }

    return  bRet;
}
void CheckPointer(void * A_pPointer, const char * A_szPointerName)
{
    G_pPointer = A_pPointer;
    G_szPointerName = A_szPointerName;
    if (!CheckPointerIternalExt())
        throw std::runtime_error("Invalid pointer " + std::string(G_szPointerName) + "!");
}

使用法:

unsigned long * pTest = (unsigned long *) 0x12345;
CheckPointer(pTest, "pTest"); //throws exception

0

IsBadReadPtr()、IsBadWritePtr()、IsBadCodePtr()、IsBadStringPtr()for Windows。
これらはブロックの長さに比例して時間がかかるので、健全性チェックのために開始アドレスをチェックするだけです。


3
これらの方法は機能しないため、避けてください。 blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx
JaredPar

時には自分の働いていないの回避策彼らのかもしれません:stackoverflow.com/questions/496034/...
ChrisW

0

さまざまなライブラリが参照されていないメモリなどをチェックするためにいくつかの方法を使用するのを見てきました。ポインタを追跡するいくつかのロジックを持つメモリ割り当ておよび割り当て解除メソッド(malloc / free)を単に「オーバーライド」すると思います。これはあなたのユースケースではやり過ぎだと思いますが、それはそれを行う1つの方法になるでしょう。


残念ながら、スタックに割り当てられたオブジェクトには役立ちません。
トム

0

技術的には、オペレーターnew(およびdelete)をオーバーライドして、割り当てられたすべてのメモリに関する情報を収集できるため、ヒープメモリが有効かどうかを確認するメソッドを使用できます。だが:

  1. ポインタがスタックに割り当てられているかどうかを確認する方法がまだ必要です()

  2. 「有効な」ポインタを定義する必要があります。

a)そのアドレスにメモリが割り当てられている

b)そのアドレスのメモリが開始はオブジェクトのアドレスです(たとえば、巨大な配列の中央にないアドレス)

c)そのアドレスのメモリは、期待されるオブジェクトの開始アドレスですタイプ

結論:問題のアプローチはC ++の方法ではありません。関数が有効なポインターを受け取ることを保証するいくつかのルールを定義する必要があります。



0

申し立てられた回答の補遺:

ポインターが保持できる値は3つだけであると想定します。0、1、-1の場合、1は有効なポインター、-1は無効なポインター、0は無効なポインターを示します。ポインター NULLである確率はどのくらいですか?1/3。ここで、有効なケースを取り出して、すべての無効なケースについて、すべてのエラーをキャッチする比率が50:50になるようにします。よさそうですね?これを4バイトのポインタにスケーリングします。可能な値は2 ^ 32または4294967294です。これらのうち、正しい値は1つだけで、1つはNULLであり、4294967292の他の無効なケースが残っています。再計算:(4294967292+ 1)の無効なケースのうちの1つのテストがあります。最も実用的な目的では、2.xe-10または0の確率。これはNULLチェックの無駄です。


0

これを実行できる新しいドライバー(少なくともLinuxでは)を書くのはそれほど難しいことではないでしょう。

一方、このようなプログラムを作成するのは愚かです。あなたがそのようなことに本当に特定の単一の用途がない限り、私はそれをお勧めしません。一定のポインター妥当性検査をロードした大きなアプリケーションを構築した場合、恐ろしく遅くなる可能性があります。


0

これらの方法は機能しないため、避けてください。blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx – JaredPar Feb 15 '09 at 16:02

それらが機能しない場合-次のWindows更新で修正されますか?コンセプトレベルで機能しない場合-関数はおそらくWindows APIから完全に削除されます。

MSDNドキュメントはそれらが禁止されていると主張し、これの理由はおそらくアプリケーションのさらなる設計の欠陥です(たとえば、通常、アプリケーション全体の設計を担当している場合、通常は無効なポインタを黙って食べてはいけません)、およびパフォーマンス/時間ポインタのチェック。

しかし、あなたはそれらがいくつかのブログのために機能しないと主張するべきではありません。私のテストアプリケーションでは、それらが機能することを確認しました。


0

これらのリンクは役に立つかもしれません

_CrtIsValidPointer指定されたメモリ範囲が読み取りと書き込みに有効であることを確認します(デバッグバージョンのみ)。 http://msdn.microsoft.com/en-us/library/0w1ekd5e.aspx

_CrtCheckMemoryデバッグヒープに割り当てられたメモリブロックの整合性を確認します(デバッグバージョンのみ)。 http://msdn.microsoft.com/en-us/library/e73x0s4b.aspx

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