测试指针的有效性(C / C ++)


90

有什么方法可以(当然,通过编程)确定给定的指针是否“有效”?检查NULL很容易,但是0x00001234之类的东西呢?尝试取消引用此类指针时,会发生异常/崩溃。

最好使用跨平台方法,但也可以使用特定于平台的方法(对于Windows和Linux)。

更新说明: 问题不是陈旧/已释放/未初始化的指针;相反,我正在实现一个API,该API接收来自调用者的指针(例如,指向字符串的指针,文件句柄等)。调用者可以发送(有意或无意)无效值作为指针。如何防止车祸?



我认为Linux的最佳肯定答案是George Carrette。如果这还不够,请考虑将功能符号表构建到库中,甚至将可用库的另一层表及其自己的功能表构建到库中。然后对照那些确切的表进行检查。当然,这些否定答案也是正确的:除非您对用户应用程序施加了许多其他限制,否则您不能真正100%确定函数指针是否有效。
minghua 2015年

API规范实际上是否指定了实现必须满足的义务?顺便说一句,我假装没有假定您既是开发人员又是设计人员。我的观点是,我不认为API会指定类似“在将无效指针作为参数传递的情况下,该函数必须处理该问题并返回NULL”的内容。API承担在适当使用条件下(而非通过黑客)提供服务的义务。然而,有点愚蠢无害。使用参考可以减少此类情况的严重破坏。:)
Poniros

Answers:


75

更新说明:问题不是陈旧的,释放的或未初始化的指针。相反,我正在实现一个API,该API接收来自调用者的指针(例如,指向字符串的指针,文件句柄等)。调用者可以发送(有意或无意)无效值作为指针。如何防止车祸?

你不能做那张支票。根本无法检查指针是否为“有效”。您必须相信,当人们使用带有指针的函数时,这些人知道他们在做什么。如果它们将0x4211作为指针值传递给您,那么您必须信任它指向地址0x4211。而且,如果它们“意外地”击中了某个对象,那么即使您使用一些令人恐惧的操作系统功能(IsValidPtr或其他功能),您仍然会陷入bug之中并且不会快速失败。

开始使用空指针来发信号通知这种事情,并告诉您的库用户,如果它们倾向于意外地传递无效指针,则不要使用指针,严重的是:)


这可能是正确的答案,但是我认为一个简单的函数可以检查通用的hexspeak内存位置,这对于常规调试很有用...现在我有了一个有时指向0xfeeefeee的指针,如果我有一个简单的函数,我可以习惯于在周围断言这将使罪魁祸首...编辑:虽然我猜自己写起来并不难
。–

@quant的问题是,某些C和C ++代码可以对无效地址执行指针算术而无需检查(基于垃圾回收,垃圾清理原则),因此将从其中一个井中传入“经过算术修改”的指针已知的无效地址。常见的情况是,根据无效的对象地址或错误的类型之一从不存在的vtable中查找方法,或者只是从指针中读取未指向一个结构的结构的字段。
rwong 2014年

这基本上意味着您只能从外界获取数组索引。必须保护自己免受调用者攻击的API只能在接口中没有指针。但是,最好在声明指针有效性的断言中使用宏(您必须在内部拥有这些宏)。如果保证指针指向其起始点和长度已知的数组内部,则可以显式检查。与断言(未记录的错误)相比,断言是违反声明(记录的错误)更好。
罗布

34

对于Linux下的C程序,以下三种简单的方法可以对其运行所在的内存状态进行自省,以及在某些情况下为什么该问题具有适当的复杂答案。

  1. 调用getpagesize()并将指针四舍五入到页面边界之后,可以调用mincore()来确定页面是否有效以及该页面是否恰好是流程工作集中的一部分。请注意,这需要一些内核资源,因此您应该对其进行基准测试,并确定在您的api中调用此函数是否确实合适。如果您的api将要处理中断,或从串行端口读入内存,则应调用此函数以避免不可预测的行为。
  2. 调用stat()确定是否存在/ proc / self目录之后,您可以打开并阅读/ proc / self / maps来查找有关指针所在区域的信息。研究proc的手册页,过程信息伪文件系统。显然,这是相对昂贵的,但是您可以避免将解析结果缓存到可以使用二进制搜索有效查找的数组中。还考虑/ proc / self / smaps。如果您的api用于高性能计算,则程序将希望了解/ proc / self / numa,该文件记录在非均匀内存体系结构numa的手册页中。
  3. get_mempolicy(MPOL_F_ADDR)调用适用于高性能计算API工作,该工作有多个执行线程,并且您正在管理您的工作以使其对CPU内核和套接字资源相关的非均匀内存具有亲和力。这样的api当然也会告诉您指针是否有效。

在Microsoft Windows下,有一个QueryWorkingSetEx函数,该函数记录在“进程状态API”下(也在NUMA API中)。作为复杂NUMA API编程的必然结果,此功能还使您可以进行简单的“有效性测试指针(C / C ++)”工作,因此,至少15年不建议使用此功能。


12
第一个答案并非试图对问题本身讲道德,实际上可以完美地回答它。人们有时没有意识到真正需要这种调试方法来在例如第三方库或旧版代码中查找错误,因为即使valgrind只能在实际访问它们时才找到野生指针,而不是例如,如果您想定期检查指针的有效性在缓存表中,这些缓存表已从您的代码中的其他位置覆盖...
lumpidu 2015年

这应该是公认的答案。我是在非Linux平台上完成的。从根本上讲,它会将流程信息公开给流程本身。在这方面,通过进程状态API公开更有意义的信息,看起来Windows比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
如果将“有效指针”定义为“不会导致访问冲突/段错误”,那么这只是“正确答案”。我希望将其定义为“指向为您将要使用的目的分配的有意义的数据的点”。我认为这是对指针有效性的更好定义...;)
杰夫

即使指针有效,也无法通过这种方式检查。想想thread1(){.. if(IsValidPtr(p))* p = 7; ...} thread2(){sleep(1); 删除p; ...}
Christopher

2
@Christopher,非常正确。我应该说“我可以在已经过去的时间读取内存中的特定位置”
JaredPar

@JaredPar:真的很糟糕的建议。可以触发保护页面,因此堆栈不会在以后扩展或同样好。
Deduplicator

16

AFAIK没有办法。您应该尝试通过在释放内存后始终将指针设置为NULL来避免这种情况。


4
将指针设置为null不会给您带来任何好处,除了可能会带来错误的安全感。

那是不对的。特别是在C ++中,您可以通过检查是否为空来确定是否删除成员对象。还要注意,在C ++中,删除空指针是有效的,因此无条件删除析构函数中的对象很普遍。
费迪南德·拜尔

4
int * p =新的int(0); int * p2 = p; 删除p; p = NULL; 删除p2; //崩溃

1
扎布宗克,还有??他说的是您可以删除一个空指针。p2不是空指针,而是无效指针。您必须先将其设置为null。
Johannes Schaub-litb

2
如果您对指向的内存有别名,则只有其中之一将被设置为NULL,其他别名会晃来晃去。
jdehaan 2011年


7

关于这个线程的答案:

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进程而不是主进程崩溃。


可能的另一种方法是使用_CrtIsValidHeapPointer。如果指针有效,则此函数将返回TRUE,并且在释放指针时将引发异常。如文档所述,此功能仅在调试CRT中可用。
Crend King

6

首先,我认为保护自己免受来电者故意造成崩溃的保护没有任何意义。他们可以通过尝试自己通过无效的指针来轻松实现此目的。还有许多其他方法-它们可能会覆盖您的内存或堆栈。如果您需要防止此类情况发生,则需要使用套接字或某些其他IPC在单独的进程中运行以进行通信。

我们编写了很多软件,可让合作伙伴/客户/用户扩展功能。不可避免地,任何错误都会首先报告给我们,因此能够轻松显示问题出在插件代码中很有用。此外,还存在安全问题,某些用户比其他用户更受信任。

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

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

  • 使用共享内存(如果要传递大量数据)来分离进程。

  • 同一进程通过消息队列(如果是频繁的短消息)将线程分开。

  • 同一进程将所有线程从内存池分配的所有线程分开。

  • 通过直接过程调用实现相同的过程-所有传递的数据均从内存池分配。

在与第三方软件打交道时,我们绝不会求助于您要做的事情-尤其是当我们以二进制文件而不是源代码的形式获得插件/库时。

在大多数情况下,使用内存池非常容易,并且效率不高。如果您首先分配数据,则对照分配的值检查指针很简单。您还可以存储分配的长度,并在数据前后添加“魔术”值,以检查有效的数据类型和数据超限。


4

我很同情你的问题,因为我本人的职位几乎相同。我很欣赏很多答复所说的话,而且它们是正确的-提供指针的例程应该提供有效的指针。就我而言,几乎无法想象他们可能损坏了指针-但是,如果他们进行了管理,那将是我的软件崩溃,而我将受到指责:-(

我的要求不是在出现分段错误后继续运行-这很危险-我只想在终止之前向客户报告发生的事情,以便他们可以修复其代码而不是怪我!

这是我发现的方法(在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,从而可能导致资源泄漏。
塞巴斯蒂安·马赫

有趣-在这种情况下还有另一种方法可以巧妙地终止吗?退出语句是这样做的唯一问题吗?我注意到我获得了“ -1”-是因为“退出”吗?
Mike Sadler

糟糕,我知道这是一种非常特殊的情况。我刚看到exit(),我的便携式C ++警铃开始响起。在这种特定于Linux的情况下应该没问题,因为您的程序无论如何都会退出,对此感到抱歉。
塞巴斯蒂安·马赫

1
signal(2)不可移植。使用sigaction(2)。 man 2 signal在Linux上有一段解释了为什么。
rptb1 2015年

1
在这种情况下,我通常将其称为abort(3)而不是exit(3),因为它更有可能产生某种调试回溯,可用于在事后诊断问题。在大多数Unixen上,abort(3)将转储核心(如果允许进行核心转储),而在Windows上,它将安装启动调试器(如果已安装)。
rptb1 2015年

4

在Unix上,您应该能够利用内核syscall进行指针检查并返回EFAULT,例如:

#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或在正确的地址上将errno设置为2,但是内核更改可能会交换验证顺序。
普雷斯顿

@Preston在DB2中,我认为我们曾经使用过unistd.h的access()。我在上面使用过open(),因为它不太晦涩,但是您可能是对的,因为有很多可能使用的系统调用。Windows曾经有一个显式的指针检查API,但事实证明它不是线程安全的(我认为它使用SEH尝试编写然后恢复内存范围的边界。)
Peeter Joot

2

C ++中没有任何规定可以测试指针作为一般情况的有效性。显然,可以假设NULL(0x00000000)是不好的,并且各种编译器和库都喜欢在此处和此处使用“特殊值”来简化调试(例如,如果我在Visual Studio中看到一个指针显示为0xCECECECE,我知道我做错了),但事实是,由于指针只是内存中的索引,因此仅通过查看指针是否为“正确”索引就几乎无法分辨。

您可以使用dynamic_cast和RTTI进行多种技巧,以确保所指向的对象具有所需的类型,但是所有这些都要求您首先要指向有效的内容。

如果要确保程序可以检测到“无效”指针,那么我的建议是:将创建时立即声明的每个指针设置为NULL或有效地址,并在释放指向其的内存后立即将其设置为NULL。如果您对这种做法很勤奋,那么只需检查NULL。


C ++(或C)中的空指针常量由常量整数零表示。许多实现使用全二进制零来表示它,但这并不是值得依靠的。
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);
}

当然,更新后的问题会使我的答案错误(或者至少是对另一个问题的答案)。我同意基本上说的答案,如果他们滥用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)
}

回复:“传递普通的数据类型...像字符串一样”但是,在C ++中,字符串通常作为字符(char *)或(const char *)的指针传递,因此您将返回传递指针。并且您的示例将in_person作为引用而不是指针传递,因此比较(in_person!= NULL)表示在Person类中定义了一些对象/指针比较。
杰西·奇斯霍尔姆

@JesseChisholm字符串表示字符串,即std :: string。我绝不建议使用char *作为存储字符串或传递字符串的方法。不要那样做
Daniel Daranas 2014年

@JesseChisholm由于某种原因,五年前回答这个问题时我犯了一个错误。显然,检查Person&是否为NULL没有意义。那甚至不会编译。我的意思是使用指针,而不是引用。我现在修复了。
Daniel Daranas 2014年

1

正如其他人所说,您不能可靠地检测到无效的指针。考虑无效指针可能采用的某些形式:

您可能有一个空指针。那是您可以轻松检查并执行某些操作的一个。

您可能有一个指向有效内存之外的地方的指针。构成有效内存的内容因系统的运行时环境如何设置地址空间而异。在Unix系统上,它通常是一个虚拟地址空间,从0开始,到大量兆字节。在嵌入式系统上,它可能很小。无论如何,它可能都不以0开始。如果您的应用恰好在超级用户模式或等效模式下运行,则您的指针可能引用真实地址,该地址可能会或可能不会使用真实内存进行备份。

您可能有一个指向有效内存中某处的指针,甚至在您的数据段,bss,堆栈或堆中,但没有指向有效对象。此方法的一种变体是一个指针,用于在对象发生不良情况之前指向有效对象。在这种情况下,坏事包括释放,内存损坏或指针损坏。

您可以拥有一个完全合法的非法指针,例如一个被非法引用的指针。

当您考虑基于段/偏移的体系结构和其他奇数指针实现时,该问题会变得更加严重。通常,良好的编译器和对类型的明智使用会对开发人员隐藏这种情况,但是,如果您想揭开面纱,并试图超越操作系统和编译器开发人员,那么可以,但是,没有一种通用的方法这样做可以解决您可能遇到的所有问题。

您能做的最好的事情是允许崩溃并发出一些好的诊断信息。


回复:“拿出一些好的诊断信息”,有摩擦。由于您无法检查指针的有效性,因此您必须大惊小怪的信息很少。您可能会得到“这里发生了异常”。整个调用栈都很不错,但是需要比大多数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

令人难以置信的是,您在以上文章中阅读了多少误导性信息...

甚至在Microsoft 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=c&&opp = 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();

这从指针获取类名-我认为它应该足以满足您的需求。

我仍然担心的一件事是指针检查的性能-在上面的代码片段中,已经进行了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

我已经看到各种库使用某种方法来检查未引用的内存等。我相信他们只是简单地“覆盖”了内存分配和释放方法(malloc / free),这些方法具有跟踪指针的逻辑。我认为这对于您的用例来说是多余的,但这将是实现此目的的一种方法。


不幸的是,这对堆栈分配的对象没有帮助。
汤姆(Tom)2009年

0

从技术上讲,您可以重写operator new(和delete)并收集有关所有已分配内存的信息,因此您可以使用一种方法来检查堆内存是否有效。但:

  1. 您仍然需要一种方法来检查是否在堆栈()上分配了指针

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

a)分配该地址上的内存

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

c)该地址处的内存是预期类型的对象的起始地址

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



0

附有答案的答案:

假设您的指针只能包含三个值-0、1和-1,其中1表示有效指针,-1表示无效指针,0表示另一个无效指针。您的指针 NULL(所有值均相等)的概率多少?1/3。现在,取出有效案例,因此对于每个无效案例,您都有50:50的比率来捕获所有错误。看起来不错吧?将其缩放为4字节指针。有2 ^ 32或4294967294可能的值。其中,只有一个值是正确的,一个是NULL,并且仍然剩下4294967292其他无效情况。重新计算:您对(4294967292 + 1)个无效案例中的1个进行了测试。对于大多数实际用途,概率为2.xe-10或0。这就是NULL检查的徒劳。


0

您知道,能够做到这一点的新驱动程序(至少在Linux上如此)可能并不难编写。

另一方面,构建这样的程序将是愚蠢的。除非您对这种事情有真正特定的用途,否则我不建议您这样做。如果您构建了一个装有恒定指针有效性检查的大型应用程序,那可能会非常慢。


0

您应避免使用这些方法,因为它们不起作用。blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx – JaredPar '09 Feb 15'在16:02

如果它们不起作用-下一个Windows Update将解决此问题吗?如果它们在概念级别上不起作用-函数可能会完全从Windows api中删除。

MSDN文档声称它们被禁止,其原因很可能是应用程序进一步设计的缺陷(例如,通常,您不应默默地吃掉无效的指针-如果您当然负责整个应用程序的设计)以及性能/时间指针检查。

但是您不应该因为某些博客而声称它们不起作用。在我的测试应用程序中,我已经验证了它们确实起作用。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.