【问题标题】:Testing pointers for validity (C/C++)测试指针的有效性 (C/C++)
【发布时间】:2010-10-07 18:07:40
【问题描述】:

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

首选跨平台方法,但特定平台(适用于 Windows 和 Linux)也可以。

更新说明: 问题不在于陈旧/释放/未初始化的指针;相反,我正在实现一个 API,它从调用者那里获取指针(如指向字符串的指针、文件句柄等)。调用者可以(有意或错误地)发送一个无效值作为指针。如何防止崩溃?

【问题讨论】:

  • 我认为 linux 最好的肯定答案是 George Carrette 给出的。如果这还不够,请考虑将函数符号表构建到库中,或者甚至是具有自己的函数表的可用库的另一个级别的表。然后检查这些确切的表格。当然,那些否定的答案也是正确的:除非您对用户应用程序设置许多额外的限制,否则您无法真正 100% 确定函数指针是否有效。
  • API 规范是否真的指定了实现必须满足的这种义务?顺便说一句,我假装没有假设您既是开发人员又是设计师。我的观点是,我不认为 API 会指定类似“如果将无效指针作为参数传递,函数必须处理问题并返回 NULL。”。 API 有义务在适当的使用条件下提供服务,而不是通过黑客攻击。尽管如此,有点愚蠢也没有坏处。使用参考可以减少此类案件的破坏性传播。 :)

标签: c++ c validation pointers null


【解决方案1】:

这篇文章MEM10-C. Define and use a pointer validation function 说可以在某种程度上进行检查,尤其是在 Linux 操作系统下。

链接中描述的方法是跟踪 malloc 返回的最高内存地址,并添加一个函数来测试是否有人尝试使用大于该值的指针。这可能是我们有限的。

【讨论】:

    【解决方案2】:

    Peeter Joos 的回答非常好。这是一种“官方”的方式:

    #include <sys/mman.h>
    #include <stdbool.h>
    #include <unistd.h>
    
    bool is_pointer_valid(void *p) {
        /* get the page size */
        size_t page_size = sysconf(_SC_PAGESIZE);
        /* find the address of the page that contains p */
        void *base = (void *)((((size_t)p) / page_size) * page_size);
        /* call msync, if it returns non-zero, return false */
        int ret = msync(base, page_size, MS_ASYNC) != -1;
        return ret ? ret : errno != ENOMEM;
    }
    

    【讨论】:

      【解决方案3】:

      在 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
      

      【讨论】:

        【解决方案4】:

        以下在 Windows 中确实有效(之前有人建议过):

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

        函数必须是某个类的静态、独立或静态方法。 要测试只读,请复制本地缓冲区中的数据。 要在不修改内容的情况下测试写入,请将它们重写。 您只能测试第一个/最后一个地址。 如果指针无效,控制权将传递给'doSomething', 然后在括号外。 只是不要使用任何需要析构函数的东西,比如 CString。

        【讨论】:

          【解决方案5】:

          在 Unix 上,您应该能够利用内核系统调用来进行指针检查并返回 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() [也许是访问] 更好的系统调用,因为这有可能导致实际的文件创建代码路径,以及随后的关闭要求。

          【讨论】:

          • 这是一个绝妙的技巧。我很乐意看到有关不同系统调用的建议以验证内存范围,特别是如果它们可以保证不会产生副作用。您可以保持打开文件描述符以写入 /dev/null 以测试缓冲区是否在可读内存中,但可能有更简单的解决方案。我能找到的最好的是 symlink(ptr,"") 它将在错误地址上将 errno 设置为 14,在好地址上设置为 2,但内核更改可能会交换验证顺序。
          • @Preston 在 DB2 中,我想我们曾经使用 unistd.h 的 access()。我在上面使用了 open() ,因为它不那么晦涩难懂,但是您可能是对的,有很多可能的系统调用可供使用。 Windows 曾经有一个显式的指针检查 API,但结果证明它不是线程安全的(我认为它使用 SEH 尝试写入然后恢复内存范围的边界。)
          • 我已经用 open() 和 access() 编译了你的代码。两者都可以工作,并且 access() 确实应该更快。
          【解决方案6】:

          确实,可以在特定场合下做一些事情:例如,如果你想检查一个字符串指针 string 是否有效,使用 write(fd, buf, szie) 系统调用可以帮助你做到这一点:让 fd 成为一个文件您为测试创建的临时文件的描述符,以及指向您正在测试的字符串的 buf,如果指针无效,write() 将返回 -1 并且 errno 设置为 EFAULT,这表明 buf 在您可访问的地址空间之外。

          【讨论】:

            【解决方案7】:

            在公共 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 * 作为存储字符串或传递字符串的方法。不要那样做。
            • @JesseChisholm 出于某种原因,五年前我回答这个问题时犯了一个错误。显然,检查 Person& 是否为 NULL 是没有意义的。那甚至不会编译。我的意思是使用指针,而不是引用。我现在修好了。
            【解决方案8】:

            下面是三种简单的方法,让 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,它记录在 Process Status API 下(也在 NUMA API 中)。 作为复杂的 NUMA API 编程的必然结果,此功能还可以让您进行简单的“测试指针的有效性 (C/C++)”工作,因此它不太可能在至少 15 年内被弃用。

            【讨论】:

            • 第一个答案不试图对问题本身进行道德化并且实际上完美地回答了它。人们有时没有意识到人们真的需要这种调试方法来发现错误。 3rd 方库或遗留代码,因为即使是 valgrind 也只能在实际访问它们时找到野指针,而不是例如如果您想定期检查缓存表中指针的有效性,这些指针已从代码中的其他位置被覆盖...
            • 这应该是公认的答案。我在非Linux平台上做了类似的事情。从根本上说,它是将流程信息暴露给流程本身。在这方面,通过进程状态 API 公开更有意义的信息,看起来 windows 比 linux 做得更好。
            • 唯一测试的是指针是可解引用的。如果它是一个指向 C 字符串的指针(读取:未知大小的内存块),该字符串被释放而没有将指针设置为 null(free 不一定这样做),并且 malloc 为一个更短的内存块分配了一个整数数组?它仍然指向垃圾,并且在寻找字符串末尾的零时可能会出现分段错误。
            【解决方案9】:

            这些链接可能会有所帮助

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

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

            【讨论】:

              【解决方案10】:

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

              如果它们不起作用 - 下一次 Windows 更新会修复它吗? 如果它们在概念级别上不起作用 - 功能可能会完全从 Windows api 中删除。

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

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

              【讨论】:

                【解决方案11】:

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

                甚至在 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.cpp&obid=58895&obtid=2&ovid=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++ 调用相比,那就太好了。

                【讨论】:

                  【解决方案12】:

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

                  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),因此您无法在任何平台上可靠地防范它。

                  【讨论】:

                    【解决方案13】:

                    澄清更新:问题不在于过时、已释放或未初始化的指针;相反,我正在实现一个 API,它从调用者那里获取指针(如指向字符串的指针、文件句柄等)。调用者可以(有意或错误地)发送一个无效值作为指针。如何防止崩溃?

                    你不能做那个检查。您根本无法检查指针是否“有效”。您必须相信,当人们使用带指针的函数时,这些人知道他们在做什么。如果他们将 0x4211 作为指针值传递给您,那么您必须相信它指向地址 0x4211。如果他们“不小心”撞到了一个物体,那么即使你使用了一些可怕的操作系统函数(IsValidPtr 或其他),你仍然会陷入错误并且不会很快失败。

                    开始使用空指针来发出这种信号,并告诉库的用户,如果他们倾向于不小心传递无效指针,他们不应该使用指针,认真的:)

                    【讨论】:

                    • 这可能是正确的答案,但我认为检查常见 hexspeak 内存位置的简单函数对于一般调试很有用......现在我有一个有时指向 0xfeeefeee 的指针,如果我有一个简单的函数,我可以用它来围绕断言它会更容易找到罪魁祸首......编辑:虽然我想自己写一个并不难......
                    • @quant 问题是一些 C 和 C++ 代码可以在不检查的情况下对无效地址进行指针运算(基于垃圾输入,垃圾输出原则),因此会传入一个“算术已修改”来自那些众所周知的无效地址之一的指针。常见的情况是根据无效的对象地址或错误类型之一从不存在的 vtable 中查找方法,或者只是从指向不指向一个结构的指针中读取字段。
                    • 这基本上意味着你只能从外界获取数组索引。必须保护自己免受调用者影响的 API 不能在接口中包含指针。但是,在关于指针有效性的断言中使用宏仍然会很好(您必须在内部拥有)。如果保证指针指向其起点和长度已知的数组内部,则可以显式检查。死于断言违规(记录错误)比死于 deref(未记录错误)要好。
                    【解决方案14】:

                    我很同情你的问题,因为我自己也处于几乎相同的位置。我很欣赏很多回复所说的话,它们是正确的——提供指针的例程应该提供一个有效的指针。在我的情况下,他们可能已经损坏了指针几乎是不可想象的 - 但如果他们 进行了管理,那将是我的软件崩溃,而我将受到指责:-(

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

                    这就是我发现的方法(在 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” - 仅仅是因为“退出”吗?
                    • 糟糕,我意识到这是一个非常特殊的情况。我刚看到exit(),我的便携式 C++ 警铃开始响起。在这种 Linux 特定情况下应该没问题,您的程序无论如何都会退出,对于噪音感到抱歉。
                    • signal(2) 不可移植。使用 sigaction(2)。 Linux 上的man 2 signal 有一段解释原因。
                    • 在这种情况下,我通常会调用 abort(3) 而不是 exit(3),因为它更有可能产生某种调试回溯,您可以使用它来在事后诊断问题。在大多数 Unixen 上,abort(3) 将转​​储核心(如果允许核心转储),在 Windows 上,它会提供启动调试器(如果已安装)。
                    【解决方案15】:

                    你知道,一个能够做到这一点的新驱动程序(至少在 Linux 上)可能不会那么难写。

                    另一方面,像这样构建程序是愚蠢的。除非你对这种东西有一些非常具体和单一的用途,否则我不会推荐它。如果您构建一个加载了常量指针有效性检查的大型应用程序,它可能会非常缓慢。

                    【讨论】:

                      【解决方案16】:

                      已接受答案的附录:

                      假设您的指针只能包含三个值——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 检查的徒劳。

                      【讨论】:

                        【解决方案17】:

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

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

                        您可以有一个指向有效内存之外某处的指针。有效内存的构成取决于系统的运行时环境如何设置地址空间。在 Unix 系统上,它通常是一个虚拟地址空间,从 0 开始,一直到一些兆字节。在嵌入式系统上,它可能非常小。无论如何,它可能不会从 0 开始。如果您的应用碰巧在超级用户模式或同等模式下运行,那么您的指针可能会引用一个真实地址,该地址可能会或可能不会使用真实内存进行备份。

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

                        您可能有一个完全非法的指针,例如与被引用事物的非法对齐的指针。

                        当您考虑基于段/偏移量的架构和其他奇怪的指针实现时,问题会变得更糟。这类事情通常通过良好的编译器和明智地使用类型对开发人员隐藏,但如果你想揭开面纱并试图智取操作系统和编译器开发人员,你可以,但没有一种通用的方法这样做可以解决您可能遇到的所有问题。

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

                        【讨论】:

                        • re:“提供一些好的诊断信息”,问题就在这里。由于您无法检查指针的有效性,因此您需要大惊小怪的信息很少。 “这里发生了一个异常,”可能就是你得到的全部。整个调用堆栈很好,但需要比大多数 C++ 运行时库提供的更好的框架。
                        【解决方案18】:

                        在 Win32/64 上有一种方法可以做到这一点。尝试读取指针并捕获将在失败时引发的生成的 SEH 异常。如果它没有抛出,那么它是一个有效的指针。

                        这个方法的问题在于它只是返回你是否可以从指针中读取数据。它不保证类型安全或任何数量的其他不变量。一般来说,除了说“是的,我可以在现在已经过去的时间读取内存中的那个特定位置”之外,这种方法几乎没有什么用处。

                        简而言之,不要这样做;)

                        Raymond Chen 有一篇关于此主题的博文:http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx

                        【讨论】:

                        • @Tim,在 C++ 中没有办法做到这一点。
                        • 如果您将“有效指针”定义为“不会导致访问冲突/段错误”,这只是“正确答案”。我更愿意将其定义为“指向为您将要使用它的目的而分配的有意义的数据”。我认为这是对指针有效性的更好定义... ;)
                        • 即使指针有效也无法通过这种方式进行检查。想想 thread1() { .. if( IsValidPtr( p ) ) *p = 7; ... } thread2() { 睡眠( 1 );删除 p; ...}
                        • @Christopher,非常正确。我应该说“我可以在现在已经过去的时间里读到记忆中的那个特定地方”
                        • @JaredPar:非常糟糕的建议。可以触发保护页,因此堆栈不会在以后扩展或其他同样好的东西。
                        【解决方案19】:

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

                        【讨论】:

                          【解决方案20】:

                          关于这个帖子中的答案有点:

                          适用于 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 中可用。
                          【解决方案21】:

                          首先,我认为保护自己免受故意造成崩溃的调用者的影响没有任何意义。他们可以通过自己尝试通过无效指针进行访问来轻松做到这一点。还有很多其他方法——它们可能只是覆盖你的内存或堆栈。如果您需要防止此类事情发生,那么您需要使用套接字或其他 IPC 在单独的进程中运行以进行通信。

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

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

                          • 使用套接字分隔进程(通常以文本形式传递数据)。

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

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

                          • 同一个进程分离线程所有传递从内存池分配的数据。

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

                          在处理第三方软件时,我们绝不会求助于您尝试做的事情 - 特别是当我们以二进制而不是源代码的形式提供插件/库时。

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

                          【讨论】:

                            【解决方案22】:

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

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

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

                            a) 该地址上的内存是 已分配

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

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

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

                            【讨论】:

                              【解决方案23】:

                              我见过各种库使用一些方法来检查未引用的内存等。我相信他们只是“覆盖”了内存分配和释放方法(malloc/free),它有一些跟踪指针的逻辑。我想这对您的用例来说有点过头了,但这是一种方法。

                              【讨论】:

                              • 不幸的是,这对堆栈分配的对象没有帮助。
                              【解决方案24】:

                              在使用之前和之后将指针设置为 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,让他们崩溃。你无法阻止人们用锤子敲自己的拇指......
                              【解决方案25】:

                              没有任何可移植的方式来做到这一点,并且为特定平台做这件事可能是困难和不可能之间的任何地方。在任何情况下,您都不应该编写依赖于此类检查的代码 - 首先不要让指针采用无效值。

                              【讨论】:

                                【解决方案26】:

                                防止调用者发送无效指针导致的崩溃是一个很好的方法来制造难以发现的静默错误。

                                对于使用您的 API 的程序员来说,通过崩溃而不是隐藏代码来获得明确的信息,表明他的代码是伪造的不是更好吗?

                                【讨论】:

                                • 不过,在某些情况下,当调用 API 时立即检查是否有错误的指针你早早失败的方式。例如,如果 API 将指针存储在一个数据结构中,它只会在以后被引用怎么办?然后向 API 传递一个错误的指针会在稍后的某个随机时间点导致崩溃。在这种情况下,最好在最初引入错误值的 API 调用处尽早失败。
                                【解决方案27】:

                                C++ 中没有规定测试指针的有效性作为一般情况。显然可以假设 NULL (0x00000000) 不好,并且各种编译器和库喜欢在这里和那里使用“特殊值”以使调试更容易(例如,如果我在 Visual Studio 中看到指针显示为 0xCECECECE 我知道我做错了什么)但事实是,由于指针只是内存中的索引,因此仅通过查看指针来判断它是否是“正确的”索引几乎是不可能的。

                                您可以使用 dynamic_cast 和 RTTI 执行各种技巧,例如确保指向的对象是您想要的类型,但它们都要求您首先指向有效的对象。

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

                                【讨论】:

                                • C++(或 C,就此而言)中的空指针常量由常量整数零表示。许多实现都使用全二进制零来表示它,但这不是可以指望的。
                                【解决方案28】:

                                适用于 Windows 的 IsBadReadPtr()、IsBadWritePtr()、IsBadCodePtr()、IsBadStringPtr()。
                                这些花费的时间与块的长度成正比,所以为了进行完整性检查,我只检查起始地址。

                                【讨论】:

                                【解决方案29】:

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

                                【讨论】:

                                • 将指针设置为 null 不会给您带来任何好处,除了可能会产生一种虚假的安全感。
                                • 这不是真的。特别是在 C++ 中,您可以通过检查 null 来确定是否删除成员对象。另请注意,在 C++ 中,删除空指针是有效的,因此在析构函数中无条件删除对象很流行。
                                • int * p = new int(0);整数 * p2 = p;删除 p; p = 空;删除 p2; // 崩溃
                                • zabzonk 和??他说的是你可以删除一个空指针。 p2 不是空指针,而是无效指针。您必须先将其设置为 null。
                                • 如果你有指向内存的别名,只有其中一个会被设置为 NULL,其他的别名就悬空了。
                                猜你喜欢
                                • 1970-01-01
                                • 1970-01-01
                                • 2018-09-20
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                相关资源
                                最近更新 更多