与えられたポインターが「有効」であるかどうかを(もちろん、プログラムで)決定する方法はありますか? NULLの確認は簡単ですが、0x00001234などはどうでしょうか?この種のポインターを逆参照しようとすると、例外/クラッシュが発生します。
クロスプラットフォームの方法が推奨されますが、プラットフォーム固有(WindowsおよびLinux)も問題ありません。
説明のための更新:問題は、古くなった/解放された/初期化されていないポインターにはありません。代わりに、呼び出し元からのポインター(文字列へのポインター、ファイルハンドルなど)を取得するAPIを実装しています。呼び出し側は、無効な値をポインターとして(意図的または誤って)送信できます。クラッシュを防ぐにはどうすればよいですか?
説明のための更新:問題は、古くなった、解放された、または初期化されていないポインターにはありません。代わりに、呼び出し元からのポインター(文字列へのポインター、ファイルハンドルなど)を取得するAPIを実装しています。呼び出し側は、無効な値をポインターとして(意図的または誤って)送信できます。クラッシュを防ぐにはどうすればよいですか?
そのチェックを行うことはできません。ポインタが「有効」であるかどうかを確認する方法はありません。人々がポインターを取る関数を使用するとき、それらの人々は彼らがしていることを知っていることを信頼しなければなりません。ポインタ値として0x4211が渡される場合、アドレス0x4211を指すことを信頼する必要があります。そして、彼らが「偶然」オブジェクトにぶつかった場合、恐ろしいオペレーションシステム機能(IsValidPtrなど)を使用したとしても、バグに陥り、すぐに失敗することはありません。
この種のことを通知するためにヌルポインターの使用を開始し、ライブラリのユーザーに、誤って無効なポインターを渡してしまう傾向がある場合は、ポインターを使用するべきではないことを真剣に伝えます:)
呼び出し側が無効なポインターを送信することによって引き起こされるクラッシュを防ぐことは、見つけにくいサイレントバグを作成する良い方法です。
APIを使用しているプログラマーが、コードを非表示にするのではなく、クラッシュさせることでコードが偽物であるという明確なメッセージを取得する方が良いと思いませんか?
Win32/64では、これを行う方法があります。ポインターを読み取って、失敗した場合にスローされる結果のSEH例外をキャッチします。スローしない場合は、有効なポインターです。
ただし、このメソッドの問題は、ポインタからデータを読み取ることができるかどうかを返すだけです。型の安全性やその他の不変条件については保証しません。一般に、この方法は、「はい、メモリ内のその特定の場所を、現在通過した時点で読み取ることができます」と言う以外にほとんど役に立ちません。
要するに、これをしないでください;)
Raymond Chenには、このテーマに関するブログ投稿があります。 http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx
LinuxでCプログラムが実行されているメモリの状態を内省する3つの簡単な方法と、一部のコンテキストで質問に適切な洗練された答えがある理由を以下に示します。
Microsoft Windowsには、Process Status API(NUMA APIでも)で文書化されているQueryWorkingSetEx関数があります。洗練されたNUMA APIプログラミングの当然の結果として、この関数は少なくとも15年間は非推奨とはなりそうもないため、単純な「有効性(C/C++)のポインターのテスト」作業を行うこともできます。
私の知る限り、方法はありません。メモリを解放した後は、常にポインターをNULLに設定して、この状況を回避するようにしてください。
このスレッドの答えについては、少し上です:
Windows用のIsBadReadPtr()、IsBadWritePtr()、IsBadCodePtr()、IsBadStringPtr().
私のアドバイスは、彼らに近づかないことです。誰かがすでにこれを投稿しています: 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プロセスをクラッシュさせる価値があるかもしれません。
第一に、意図的にクラッシュを引き起こそうとしている呼び出し元から自分自身を保護しようとする意味はありません。無効なポインタを介してアクセスしようとすると、簡単にこれを行うことができます。他にも多くの方法があります-メモリやスタックを上書きするだけです。この種のことから保護する必要がある場合は、ソケットまたは他のIPC=通信用に使用する別のプロセスで実行する必要があります。
パートナー/顧客/ユーザーが機能を拡張できるようにするソフトウェアを非常に多く作成しています。必ずバグが最初に報告されるため、問題がプラグインコードにあることを簡単に示すことができると便利です。さらに、セキュリティ上の懸念があり、一部のユーザーは他のユーザーよりも信頼されています。
パフォーマンス/スループット要件と信頼性に応じて、さまざまな方法を使用します。最も好ましいものから:
ソケットを使用するプロセスを分離します(多くの場合、データをテキストとして渡します)。
共有メモリを使用する個別のプロセス(大量のデータを渡す場合)。
同じプロセスは、メッセージキューを介してスレッドを分離します(短いメッセージが頻繁に発生する場合)。
同じプロセスが、メモリプールから割り当てられたすべてのデータを渡すスレッドを分離します。
直接プロシージャコールによる同じプロセス-メモリプールから割り当てられたすべての渡されたデータ。
サードパーティのソフトウェアを扱うとき、特にプラグイン/ライブラリがソースコードではなくバイナリとして提供されている場合、あなたがやろうとしていることに頼らないようにします。
メモリプールの使用は、ほとんどの状況で非常に簡単であり、非効率的である必要はありません。最初にデータを割り当てる場合、割り当てた値に対してポインターをチェックするのは簡単です。割り当てられた長さを保存し、データの前後に「マジック」値を追加して、有効なデータ型とデータオーバーランをチェックすることもできます。
私は自分とほぼ同じ立場にいるので、あなたの質問には多くの同情を持っています。私は多くの回答が言っていることに感謝し、それらは正しいです-ポインターを提供するルーチンshouldは有効なポインターを提供しています。私の場合、ポインタが破損している可能性はほとんど考えられませんが、had管理されている場合、クラッシュするのは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);
}
これはappears期待通りに動作します(エラーメッセージを出力し、プログラムを終了します)-誰かが欠陥を発見できる場合はお知らせください!
パブリック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)
}
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があります。これは実際のファイル作成コードパスとそれに続くクローズ要件につながる可能性があるためです。
これを実行するための移植可能な方法はありません。特定のプラットフォームで実行することは、難しいことも不可能なこともあります。いずれにせよ、このようなチェックに依存するコードを書くべきではありません-そもそもポインターが無効な値をとらないようにしてください。
一般的なケースとして、ポインターの有効性をテストするためのC++の規定はありません。 NULL(0x00000000)が悪いことは明らかであり、さまざまなコンパイラやライブラリは、デバッグを容易にするためにあちこちで "特別な値"を使用することを好みます(たとえば、Visual Studioでポインタが0xCECECECEとして表示される場合私は何か間違ったことをしましたが)真実は、ポインターはメモリへの単なるインデックスであるため、「正しい」インデックスであるかどうかをポインターだけで見分けることはほぼ不可能です。
Dynamic_castとRTTIを使用して、指し示されているオブジェクトが目的のタイプであることを確認するためのさまざまなトリックがありますが、それらはすべて、最初から有効なものを指している必要があります。
あなたがプログラムが「無効な」ポインタを検出できるようにしたいなら、私のアドバイスはこれです:あなたが宣言するすべてのポインタを作成直後にNULLまたは有効なアドレスに設定し、それが指すメモリを解放した直後にNULLに設定します。この方法に熱心であれば、NULLのチェックだけで十分です。
使用の前後にポインターを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);
}
この記事 MEM10-C。ポインター検証関数の定義と使用 は、特にLinux OSである程度のチェックを行うことができると述べています。
他の人が言ったように、無効なポインターを確実に検出することはできません。無効なポインターが取る可能性のあるいくつかの形式を検討してください。
NULLポインターを使用できます。それはあなたが簡単にチェックして何かをすることができるものです。
有効なメモリ外のどこかにポインタを置くことができます。有効なメモリを構成するものは、システムのランタイム環境がアドレス空間をどのように設定するかによって異なります。 Unixシステムでは、通常は0から始まり、数メガバイトになる仮想アドレス空間です。組み込みシステムでは、かなり小さい場合があります。いずれの場合も、0から開始しない場合があります。アプリがスーパーバイザーモードまたは同等のモードで実行されている場合、ポインターは実際のアドレスを参照している可能性があり、実際のメモリでバックアップされている場合とされていない場合があります。
データセグメント、bss、スタック、またはヒープ内であっても、有効なメモリ内のどこかにポインターを置くことができますが、有効なオブジェクトを指していません。これの変形は、オブジェクトに何か悪いことが起こる前に、有効なオブジェクトを指すために使用されるポインターです。このコンテキストでの悪いことには、割り当て解除、メモリ破損、またはポインタ破損が含まれます。
参照されているものの不正なアライメントを持つポインターなど、フラットアウトの不正なポインターがある可能性があります。
この問題は、セグメント/オフセットベースのアーキテクチャやその他の奇妙なポインターの実装を考慮するとさらに悪化します。この種のことは通常、適切なコンパイラーと型の賢明な使用によって開発者から隠されていますが、ベールを突き刺してオペレーティングシステムとコンパイラーの開発者を裏切りたい場合は、できますが、1つの一般的な方法はありません実行する可能性のあるすべての問題を処理するためにそれを行います。
あなたができる最善のことは、クラッシュを許可し、いくつかの良い診断情報を出すことです。
実際、特定の状況下で何かを行うことができます:たとえば、文字列ポインター文字列が有効かどうかを確認したい場合、write(fd、buf、szie)syscallを使用すると魔法を実行できます:fdを一時的なファイル記述子にしますテスト用に作成したファイル、およびポインタが無効な場合、テスティングする文字列を指すbufは、write()が-1を返し、errnoがEFAULTに設定され、bufがアクセス可能なアドレス空間の外にあることを示します。
一般的に、それは不可能です。これは特に厄介なケースの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]
ランタイムシステムにメモリのビットを誤って解釈するように強制しますが、この場合、ビットはすべて意味があるので、クラッシュすることはありません。これは、言語の設計の一部です(struct inaddr
、inaddr_in
、inaddr_in6
)。したがって、どのプラットフォームでも確実に保護することはできません。
上記の記事でどれだけ誤解を招く情報を読むことができるかは信じられません...
Microsoftのmsdnドキュメントでも、IsBadPtrは禁止されていると主張されています。まあ-私はクラッシュするよりも動作するアプリケーションを好みます。エンドユーザーがアプリケーションを続行できる限り、タームワーキングが正しく機能しない場合もあります。
グーグルで私はウィンドウの有用な例を見つけていません-32ビットアプリのソリューションを見つけました、
しかし、64ビットアプリもサポートする必要があるため、このソリューションはうまくいきませんでした。
しかし、ワインのソースコードを収集し、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 ++呼び出しと比較して、誰かがポインターチェックのオーバーヘッドを測定できるとよいでしょう。
C++でそのチェックを行う方法はありません。他のコードから無効なポインターが渡された場合はどうすればよいですか? クラッシュするはずです。 どうして?このリンクを確認してください: http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx
これらのリンクは役に立つかもしれません
_CrtIsValidPointer指定されたメモリ範囲が読み取りおよび書き込みに有効であることを検証します(デバッグバージョンのみ)。 http://msdn.Microsoft.com/en-us/library/0w1ekd5e.aspx
_CrtCheckMemoryは、デバッグヒープに割り当てられたメモリブロックの整合性を確認します(デバッグバージョンのみ)。 http://msdn.Microsoft.com/en-us/library/e73x0s4b.aspx
以下はWindowsで動作します(以前に誰かが提案した):
static void copy(void * target, const void* source, int size)
{
__try
{
CopyMemory(target, source, size);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
doSomething(--whatever--);
}
}
関数は、静的、スタンドアロン、または何らかのクラスの静的メソッドである必要があります。読み取り専用でテストするには、ローカルバッファーにデータをコピーします。内容を変更せずに書き込みをテストするには、上書きします。最初/最後のアドレスのみをテストできます。ポインターが無効な場合、制御は「doSomething」に渡され、括弧の外側に渡されます。 CStringなど、デストラクタを必要とするものは使用しないでください。
回答の回答への補遺:
ポインターが保持できる値は、0、1、-1の3つだけで、1は有効なポインターを表し、-1は無効なポインターを表し、0は別の無効なポインターを表します。ポインターis NULLで、すべての値が等しくなる可能性はどのくらいですか? 1/3。ここで、有効なケースを取り出してください。すべての無効なケースについて、すべてのエラーをキャッチするための比率は50:50です。良さそうですか?これを4バイトポインターに合わせてスケーリングします。 2 ^ 32または4294967294の可能な値があります。これらのうち、正しい値は1つだけで、1つはNULLであり、4294967292の他の無効なケースが残っています。再計算:(4294967292+ 1)の無効なケースのうち1つについてテストがあります。最も実用的な目的では、2.xe-10または0の確率。これがNULLチェックの無益さです。
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
Windows用のIsBadReadPtr()、IsBadWritePtr()、IsBadCodePtr()、IsBadStringPtr().
これらはブロックの長さに比例して時間がかかるため、健全性チェックでは開始アドレスのみをチェックします。
ご存知のように、これが可能な新しいドライバー(少なくともLinuxの場合)は、おそらくそれほど難しいことではないでしょう。
一方、このようなプログラムを構築するのは愚かなことです。そのようなことを本当に具体的かつ単一に使用しない限り、お勧めしません。一定のポインターの有効性チェックでロードされた大規模なアプリケーションを構築した場合、恐ろしく遅くなります。
さまざまなライブラリが、参照されていないメモリなどをチェックするために何らかの方法を使用するのを見てきました。ポインタを追跡するロジックを備えたメモリ割り当ておよび割り当て解除メソッド(malloc/free)を単に「オーバーライド」すると信じています。私はこれがあなたのユースケースにとってはやり過ぎだと思いますが、それはそれをする一つの方法でしょう。
技術的には、演算子new(およびdelete)をオーバーライドして、割り当てられたすべてのメモリに関する情報を収集できるため、ヒープメモリが有効かどうかを確認するメソッドを使用できます。しかし:
ポインタがスタックに割り当てられているかどうかを確認する方法が必要です()
「有効な」ポインターとは何かを定義する必要があります。
a)そのアドレスのメモリが割り当てられます
b)そのアドレスのメモリはstartオブジェクトのアドレス(たとえば、巨大な配列の中央にないアドレス)
c)そのアドレスのメモリはstartオブジェクトのアドレスexpected type
最下行:問題のアプローチはC++の方法ではありません。関数が有効なポインターを受け取ることを保証するいくつかのルールを定義する必要があります。
これらのメソッドは機能しないため、避ける必要があります。 blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx – JaredPar 2009年2月15日16:02
動作しない場合は、次のウィンドウの更新で修正されますか?コンセプトレベルで機能しない場合、おそらくWindows APIから機能が完全に削除されます。
MSDNドキュメントは禁止されていると主張しており、この理由はおそらくアプリケーションのさらなる設計の欠陥です(たとえば、一般にアプリケーション全体の設計を担当している場合は、無効なポインタを黙って食べてはいけません)。ポインターチェックの。
ただし、ブログが原因で機能しないと主張するべきではありません。私のテストアプリケーションでは、それらが機能することを確認しました。