代码之家  ›  专栏  ›  技术社区  ›  noamtm

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

  •  78
  • noamtm  · 技术社区  · 17 年前

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

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

    28 回复  |  直到 17 年前
        1
  •  79
  •   Peter Mortensen Pieter Jan Bonestroo    13 年前

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

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

    开始使用空指针来表示这种情况,并告诉库的用户,如果他们无意中传递了无效指针,就不应该使用指针,说真的:)

        2
  •  42
  •   George Carrette    11 年前

    这里有三种简单的方法可以让Linux下的C程序反省它运行的内存状态,以及为什么这个问题在某些上下文中有合适的复杂答案。

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

    在Microsoft Windows下,在Process Status API(也在NUMA API中)下记录了QueryWorkingSetx函数。

        3
  •  30
  •   Nailer    17 年前

    防止因调用方发送无效指针而导致崩溃是一种很好的方法,可以使沉默的bug很难找到。

    对于使用API的程序员来说,通过崩溃而不是隐藏代码来获得一条清晰的消息,即他的代码是伪造的,这不是更好吗?

        4
  •  28
  •   Adam Rosenfield    17 年前

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

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

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

    陈雷蒙(Raymond Chen)就这一主题发表了一篇博文: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx

        5
  •  16
  •   Ferdinand Beyer    17 年前

    哎呀,没办法。您应该在释放内存后始终将指针设置为NULL,以避免出现这种情况。

        6
  •  8
  •   Peeter Joot    8 年前

    #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()[可能是access]更好的系统调用可供使用,因为这有可能导致实际的文件创建代码路径,以及后续的关闭要求。

        7
  •  7
  •   Fredrik    17 年前

    关于这个问题的答案,在这个问题上有点夸张:

    适用于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实际上应该被称为crashprogrammandomy”)。

    如果API的用户发送坏数据,让它崩溃。如果问题是传递的数据直到稍后才被使用(这使得查找原因变得更加困难),请添加一个调试模式,在该模式下,字符串等将记录在条目中。如果他们是坏的,这将是显而易见的(并且可能崩溃)。如果这种情况经常发生,那么将您的API移出进程并让它们崩溃API进程而不是主进程可能是值得的。

        8
  •  5
  •   Dipstick    17 年前

    首先,我看不出有任何必要试图保护自己不受故意试图造成撞车的来电者的伤害。他们可以通过自己尝试通过无效指针进行访问来轻松实现这一点。还有很多其他的方法——它们可以覆盖内存或堆栈。如果您需要防止这种情况,那么您需要在单独的进程中运行,使用套接字或其他IPC进行通信。

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

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

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

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

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

    在大多数情况下,使用内存池非常容易,而且不必效率低下。如果您首先分配数据,那么对照分配的值检查指针就很简单了。您还可以存储分配的长度,并在数据前后添加“magic”值,以检查有效的数据类型和数据溢出。

        9
  •  5
  •   Sebastian Mach    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);
    }
    

    现在这个 出现

        10
  •  2
  •   Toji    17 年前

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

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

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

        11
  •  2
  •   Tim Ring    17 年前

    在使用之前和之后将指针设置为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);
    }
    
        12
  •  2
  •   Vince.Wu    8 年前

        13
  •  1
  •   anon anon    17 年前

    没有任何可移植的方法可以做到这一点,在特定的平台上实现这一点可能是困难的,也可能是不可能的。在任何情况下,您都不应该编写依赖于这种检查的代码——首先不要让指针接受无效值。

        14
  •  1
  •   John Grieggs    17 年前

    正如其他人所说,您无法可靠地检测到无效指针。考虑无效指针可能需要的一些表单:

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

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

    您可能有一个完全非法的指针,例如被引用对象的指针具有非法对齐方式。

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

        15
  •  1
  •   Peter Mortensen Pieter Jan Bonestroo    13 年前

    一般来说,这是不可能做到的。这里有一个特别糟糕的案例:

    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

        16
  •  1
  •   TarmoPikaro    12 年前

    令人难以置信的是,你能在上面的文章中读到多少误导性的信息。。。

    甚至在微软的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位应用程序,所以这个解决方案不适合我。

    #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;  
    }  
    

    下面的代码可以检测指针是否有效,您可能需要添加一些空检查:

        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/托管/C++调用,那就更好了。

        17
  •  1
  •   Daniel Daranas    10 年前

    在公共API中接受任意指针作为输入参数不是一个很好的策略。最好使用“纯数据”类型,如整数、字符串或结构(当然,我指的是包含纯数据的经典结构;正式来说,任何东西都可以是结构)。

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

    你能再检查一下吗?在这种情况下,我所做的是为指针指向的类型定义一个不变量,并在得到它时调用它(在调试模式下)。至少在不变量失败(或崩溃)时,您知道传递了一个错误的值。

    // 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)
    }
    
        18
  •  1
  •   melpomene    7 年前

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

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

        19
  •  1
  •   Artkov    7 年前

    在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
    
        20
  •  1
  •   Be Kind To New Users    5 年前

    本文 MEM10-C. Define and use a pointer validation function 表示在某种程度上可以进行检查,特别是在Linux操作系统下。

        21
  •  0
  •   pngaz    17 年前


    这些所需的时间与块的长度成比例,因此为了进行健全性检查,我只需检查起始地址。

        22
  •  0
  •   sebnow    17 年前

        23
  •  0
  •   noonex noonex    17 年前

    从技术上讲,您可以覆盖运算符 刚出现的 删去 但是:

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

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

    a) 那个地址的内存不足 分配

    b) 那个地址的内存 是 开始 阵列)

    是 开始 对象地址 预期 类型

    要旨

        24
  •  0
  •   zildjohn01    17 年前

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

        25
  •  0
  •   dirkgently    17 年前

    接受答复的附录:

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

        26
  •  0
  •   dicroce    17 年前

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

        27
  •  0
  •   TarmoPikaro    12 年前

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

    如果它们不工作-下一个windows update将修复它? 如果它们不能在概念级工作,函数可能会从windows api中完全删除。

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

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

        28
  •  0
  •   Khachatur    12 年前

    这些链接可能会有所帮助

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

    _crtcheck存储器 http://msdn.microsoft.com/en-us/library/e73x0s4b.aspx

        29
  •  0
  •   Tyler Durden    5 年前

    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;
    }