testing指针的有效性(C / C ++)

有什么方法可以确定(当然,编程方式)给定的指针是否“有效”? 检查NULL很容易,但是像0x00001234这样的事情呢? 当试图解引用这种types的指针时会发生exception/崩溃。

一个跨平台的方法是首选,但特定于平台(Windows和Linux)也是可以的。

澄清更新:问题不在陈旧/释放/未初始化的指针; 相反,我正在实现一个从调用方获取指针的API(如指向string的指针,文件句柄等)。 调用者可以发送(有意或无意)一个无效值作为指针。 我如何防止崩溃?

澄清更新:问题不在于陈旧,释放或未初始化的指针; 相反,我正在实现一个从调用方获取指针的API(如指向string的指针,文件句柄等)。 调用者可以发送(有意或无意)一个无效值作为指针。 我如何防止崩溃?

你不能做这个检查。 根本没有办法可以检查指针是否“有效”。 你必须相信,当人们使用一个需要指针的函数时,这些人就知道他们在做什么。 如果他们通过0x4211作为指针值,那么你必须相信它指向地址0x4211。 如果他们“意外地”击中了一个对象,那么即使你使用了一些可怕的操作系统函数(IsValidPtr或其他),你仍然会陷入一个bug中,而不会快速失败。

开始使用空指针来发信号这种事情,并告诉你的库的用户,如果他们不小心通过无效的指针,他们不应该使用指针,严重:)

防止由调用者发送一个无效指针引起的崩溃是一个很好的方法,使沉默的错误,很难find。

使用API​​的程序员得到一个明确的信息,说他的代码是通过崩溃而不是隐藏它是不是更好?

在Win32 / 64上有办法做到这一点。 尝试读取指针,并捕获由于失败而导致的SEH泄漏。 如果它不扔,那么这是一个有效的指针。

这个方法的问题是,它只是返回你是否可以从指针读取数据。 它不保证types安全或任何其他不variables。 一般来说,这种方法除了说“是的,我可以在现在已经过去的时间里读到那个特定的地方”之外别无用处。

总之,不要这样做;)

Raymond Chen在这个问题上有一篇博文: http : //blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx

这里有三个简单的方法让Linux下的C程序能够反省运行内存的状态,以及为什么这个问题在某些情况下有适当的复杂的答案。

  1. 在调用getpagesize()并将指针四舍五入到页边界之后,可以调用mincore()来查看页面是否有效,以及是否恰好是进程工作集的一部分。 请注意,这需要一些内核资源,所以你应该对它进行基准testing,并确定在你的API中调用这个函数是否真的是合适的。 如果你的API将要处理中断,或者从串口读入内存,那么调用这个函数是为了避免不可预知的行为。
  2. 在调用stat()以确定是否有/ proc / self目录可用之后,可以打开并通读/ proc / self / maps来查找有关指针所在区域的信息。 研究proc的过程信息伪文件系统的手册页。 显然这是相对昂贵的,但是你也许能够将parsing的结果caching到一个数组中,你可以使用二进制search来高效地查找数据。 还要考虑/ proc / self / smaps。 如果你的api是用于高性能计算的,那么程序会想知道numa这个非统一的内存体系结构的手册页中logging的/ proc / self / numa。
  3. get_mempolicy(MPOL_F_ADDR)调用适用于有多个执行线程的高性能计算api工作,而且您正在pipe理工作以便与非统一内存相关,因为它与cpu内核和套接字资源相关。 这样的api当然也会告诉你指针是否有效。

在Microsoft Windows下,有一个QueryWorkingSetEx函数,它在Process Status API(也在NUMA API中)中有logging。 作为复杂的NUMA API编程的推论,这个函数也可以让你做简单的“有效性testing指针(C / C ++)”工作,至less在15年内不太可能被弃用。

AFAIK没有办法。 释放内存后,应该尽量避免出现这种情况,因为总是将指针设置为NULL。

看看这个和这个问题。 另外看看智能指针

首先,我没有看到任何意图试图保护自己免受调用者故意导致崩溃。 他们可以很容易地通过尝试访问通过一个无效的指针本身。 还有很多其他的方法 – 他们可能只是覆盖你的内存或堆栈。 如果你需要防范这种事情,那么你需要在使用套接字或其他IPC进行通信的独立进程中运行。

我们编写了相当多的软件,允许合作伙伴/客户/用户扩展function。 不可避免地,任何错误都会首先报告给我们,所以能够轻松地显示问题出现在插件代码中是非常有用的。 另外还有安全问题,有些用户比其他用户更可信。

我们根据性能/吞吐量要求和可靠性使用多种不同的方法。 从最喜欢的:

  • 使用套接字分离进程(通常将数据作为文本传递)。

  • 使用共享内存的单独进程(如果要传递大量数据的话)。

  • 相同的进程通过消息队列分离线程(如果频繁的短消息)。

  • 相同的进程单独的线程都传递从内存池分配的数据。

  • 通过直接过程调用的相同过程 – 所有通过从内存池分配的数据。

在处理第三方软件时,我们尽量不要诉诸于你所要做的事 – 特别是当我们将插件/库作为二进制文件而不是源代码。

在大多数情况下使用内存池是非常容易的,并且不需要低效。 如果您首先分配数据,那么检查针对您分配的值的指针是微不足道的。 您还可以存储分配的长度,并在数据前后添加“魔术”值,以检查有效的数据types和数据超限。

关于在这个线程中的答案:

Windows的IsBadReadPtr(),IsBadWritePtr(),IsBadCodePtr(),IsBadStringPtr()。

我的build议是远离他们,有人已经发布了这一个: 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的用户发送错误的数据,让它崩溃。 如果问题是传递的数据直到以后才被使用(这使得难以find原因),那么添加一个debugging模式,其中string等被logging在入口处。 如果他们不好,这将是显而易见的(可能会崩溃)。 如果这种情况经常发生,那么将API移出进程可能是值得的,并让他们崩溃API进程而不是主进程。

我对你的问题有很多的同情,因为我本人的立场几乎一样。 我很欣赏很多回复所说的话,它们是正确的 – 提供指针的例程应该提供一个有效的指针。 就我而言,这几乎是不可思议的,他们可能会损坏指针 – 但如果他们已经pipe理,这将是我的软件崩溃,而我会得到指责:-(

我的要求并不是我在分段错误后继续工作 – 这很危险 – 我只想在终止之前报告客户发生了什么事,以便他们能够修复他们的代码而不是责怪我!

这是我发现的做法(在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); } 

现在,这似乎performance得如我所愿(打印出错信息,然后终止程序) – 但如果有人能发现缺陷,请告诉我!

在C ++中没有规定将指针的有效性作为一般情况进行testing。 很明显可以假定NULL(0x00000000)是不好的,各种编译器和库喜欢在这里和那里使用“特殊值”来使debugging变得更容易(例如,如果我在Visual Studio中看到一个指针显示为0xCECECECE,我做了错误的事情),但事实是,由于指针只是一个内存索引,如果它是“正确的”索引,通过查看指针几乎是不可能的。

你可以用dynamic_cast和RTTI来做各种各样的技巧,以确保指向的对象是你想要的types,但是它们都要求你首先指向有效的东西。

如果你想确保你的程序可以检测到“无效”的指针,那么我的build议是这样的:在创build时立即将你声明为NULL或有效地址的每个指针设置为空,并在释放指向的内存之后立即将其设置为NULL。 如果你勤于练习,那么检查NULL是你所需要的。

没有任何可移植的方式来做到这一点,做特定的平台可以在任何困难和不可能之间的任何地方。 在任何情况下,你都不应该编写依赖于这种检查的代码 – 不要让指针在第一时间取得无效值。

在使用前后将指针设置为NULL是一种很好的技术。 例如,如果pipe理类中的指针(例如string),那么在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); } 

在公共API中接受任意指针作为input参数并不是一个好策略。 最好有一个“纯数据”types,如整数,string或结构(我的意思是一个经典的结构,里面有纯数据,当然,任何东西都可以是结构体)。

为什么? 那么因为正如其他人所说,没有一个标准的方法来知道你是否被给了一个有效的指针或指向垃圾。

但有时你没有select – 你的API必须接受一个指针。

在这些情况下,调用者有责任传递一个好的指针。 NULL可以被接受为一个值,但不是指向垃圾的指针。

你能以任何方式仔细检查吗? 那么,我在这种情况下所做的就是定义指针​​指向的types的不variables,并在debugging模式下调用它。 至less如果不variables(或崩溃),你知道你通过了一个坏的价值。

 // 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) } 

正如其他人所说,你不能可靠地检测到一个无效的指针。 考虑一些无效指针可能会用到的forms:

你可以有一个空指针。 这是一个你可以轻松地检查和做一些事情。

你可以有一个指向有效内存之外的地方。 有效内存的构成取决于系统运行时环境如何设置地址空间。 在Unix系统上,它通常是一个虚拟的地址空间,从0开始,到大量的兆字节。 在embedded式系统上,它可能很小。 无论如何,它可能不会从0开始。 如果您的应用程序恰好以超级用户模式或等同模式运行,那么您的指针可能会引用实际地址,实际地址可能会或可能不会被真实内存备份。

你可以有一个指向你的有效内存中的某个地方,甚至在你的数据段,bss,堆栈或堆中,但不指向一个有效的对象。 这个变体是一个指针,用于在对象发生什么事情之前指向一个有效的对象。 这方面的坏事包括释放,记忆腐败或指针腐败。

你可以有一个非法的非法指针,比如一个非法alignment的指针。

当考虑基于段/偏移量的体系结构和其他奇怪的指针实现时,问题变得更糟。 这种事情通常是由开发人员通过良好的编译器和对types的明智使用隐藏起来的,但是如果你想要揭开面纱,试图超越操作系统和编译器开发者,那么你可以,但是没有一个通用的方法做到这一点,将处理您可能遇到的所有问题。

你可以做的最好的事情是让崩溃,并提出一些很好的诊断信息。

一般来说,这是不可能的。 这是一个特别令人讨厌的例子:

 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] 

您迫使运行时系统错误地解释内存位,但在这种情况下,它不会崩溃,因为这些位都是有意义的。 这是语言devise的一部分(查看struct inaddrinaddr_ininaddr_in6中的C风格多态),所以在任何平台上都无法可靠地防范它。

这是令人难以置信的多less误导性的信息,你可以阅读上面的文章…

甚至在微软的MSDN文档IsBadPtr声称是被禁止的。 噢 – 我喜欢工作应用程序而不是崩溃。 即使术语工作可能不正确(只要最终用户可以继续使用应用程序)。

通过谷歌search,我还没有find任何有用的Windows示例 – find一个解决scheme的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=58895&obtid=2&ovid = 2

但我也需要支持64位应用程序,所以这个解决scheme不适合我。

但是我已经收获了葡萄酒的源代码,并设法制作类似于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(); 

这从指针得到类名 – 我认为它应该足够满足您的需求。

有一点我仍然害怕的是指针检查的性能 – 在上面的代码snipet中已经有3-4个API调用了 – 对于时间要求严格的应用程序来说可能是过度的。

如果有人可以比较指针检查的开销,比如C#/托pipe的c ++调用,那将会很好。

Windows的IsBadReadPtr(),IsBadWritePtr(),IsBadCodePtr(),IsBadStringPtr()。
这些花费的时间与块的长度成正比,所以为了理智检查,我只是检查起始地址。

我看到各种库使用一些方法来检查未被引用的内存等。 我相信他们只是“覆盖”内存分配和释放方法(malloc / free),它有一些逻辑跟踪指针。 我认为这对你的用例来说是过度的,但是这将是一个办法。

从技术上讲,你可以覆盖操作符new (和delete )并收集有关所有分配的内存的信息,所以你可以有一个方法来检查堆内存是否有效。 但:

  1. 你仍然需要一个方法来检查指针是否分配在堆栈()

  2. 你将需要定义什么是“有效的”指针:

a)分配该地址上的内存

b)该地址的内存是对象的起始地址(例如,地址不在巨大数组的中间)

c)该地址的内存是预期types的对象的起始地址

底线 :有问题的方法不是C ++的方式,你需要定义一些确保函数接收有效指针的规则。

没有办法在C ++中进行检查。 如果其他代码传递给你一个无效的指针,你应该怎么做? 你应该崩溃。 为什么? 看看这个链接: http : //blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx

补充说明:

假设你的指针只能保存三个值 – 0,1和-1,其中1表示一个有效的指针,-1表示一个无效的指针,另一个表示无效的一个。 你的指针 NULL的概率多less,所有的值都是相同的? 1/3。 现在,拿出有效的案例,所以对于每一个无效案例,你有一个50:50的比率来捕捉所有的错误。 看起来不错吧? 将其缩放为一个4字节的指针。 有2 ^ 32或4294967294可能的值。 其中只有一个值是正确的,一个是NULL,并且还剩下4294967292个其他无效的情况。 重新计算:对(4294967292 + 1)个无效案例中的1个进行testing。 大多数实际用途的概率为2.xe-10或0。 这是无效的检查。

你知道,一个新的驱动程序(至less在Linux上)可能不会那么难写。

另一方面,像这样构build你的程序将是愚蠢的。 除非你对这样的事情有一些真正的具体和单一的用途,我不会推荐它。 如果你build立了一个大的应用程序加载了恒定的指针有效性检查,它可能会非常慢。

这篇文章MEM10-C。 定义和使用一个指针validation函数表示可以在一定程度上进行检查,特别是在Linux操作系统下。

你应该避免这些方法,因为他们不工作。 blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx – JaredPar 2009年2月15日在16:02

如果他们不工作 – 下一个Windows更新将解决它? 如果他们不在概念级别工作 – function可能会完全从Windows API中删除。

MSDN文档声称,他们被禁止,原因可能是应用程序的进一步devise的缺陷(例如,一般你不应该无声地吃无效的指针 – 如果你负责整个应用程序的devise),性能/时间指针检查。

但是你不应该因为某个博客而声称他们不工作。 在我的testing应用程序中,我已经validation了他们的工作。

这些链接可能会有所帮助

_CrtIsValidPointervalidation指定的内存范围对于读写是否有效(仅限debugging版本)。 http://msdn.microsoft.com/en-us/library/0w1ekd5e.aspx

_CrtCheckMemory确认debugging堆中分配的内存块的完整性(仅限debugging版本)。 http://msdn.microsoft.com/en-us/library/e73x0s4b.aspx

以下在Windows中工作(有人build议之前):

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

该函数必须是静态的,独立的或某些类的静态方法。 要以只读方式进行testing,请在本地缓冲区中复制数据。 要在不修改内容的情况下testing写入,请将其写入。 您只能testing第一个/最后一个地址。 如果指针无效,则控件将传递给“doSomething”,然后在括号外。 只是不要使用任何需要析构函数,如CString。