C++:如何确定指针是否指向有效对象?

42

我正在学习C++并阅读《C++ Primer》。我有一个问题想要知道答案:

给定一个指针p,你能否确定p是否指向有效的对象?如果可以,那么如何确定?如果不行,为什么不行?


1
请定义“有效”。 - tckmn
3
智能指针的作用是什么。 - chris
如果您了解底层内存管理器的工作方式(如何进行簿记),那么您也许可以弄清楚它。但是,这将非常依赖于平台。如果您自己管理内存,那么这应该不是问题。最佳做法仍然是坚持最佳实践(RAII、封装)。 - didierc
@chris - 智能指针的作用有点类似。但是GIGO仍然适用;您始终可以创建一个持有错误指针的智能指针。 - Pete Becker
2
可能是 Testing pointers for validity (C/C++) 的重复问题。 - jwodder
显示剩余2条评论
6个回答

49

不行,为什么呢?因为维护关于哪些指针是有效的、哪些不是有效的元数据会很昂贵,在C++中,只要你不需要的东西就不用付出代价。

而且你不想检查指针是否有效,因为你知道指针来自哪里,无论是因为它是你控制的代码的私有部分,还是因为你在外部合约中指定了它。


我认为这并不昂贵...现在标准库中的智能指针有非常少量的开销。在绝大多数情况下,其好处远远超过成本。有关更多信息,请参见相关帖子:https://dev59.com/PGEh5IYBdhLWcg3wjD_n - tjwrona1992
@tjwrona1992:这似乎过于简单和不切实际。智能指针具有不同的语义,因此不能进行比较(unique_ptr、shared_ptr),或者它们具有相同的语义并且也存在相同的问题(observer_ptr)。还要注意,由于ABI选择不佳(它不是在寄存器中返回),在作为返回类型时,unique_ptr在实践中比原始指针更昂贵。 - Kerrek SB
我并不是说它们没有成本。我想说的是,如果你不是在嵌入式环境下工作,并且编写代码用于内存非常有限、时钟速度很慢的微处理器,那么在几乎所有的使用情况下,与收益相比,成本基本可以忽略不计。 - tjwrona1992
1
@tjwrona1992:除了真正存在的大型代码库抱怨unique_ptr带来的不必要的性能损失(在这个层面上,它会产生实际的美元价值成本)之外,您还忽略了我的主要观点:智能指针并不是一种即插即用的替代品,因为它们具有不同的语义。没有明显的智能指针可以用于非所有权转移引用语义,以匹配原始指针的语义;这需要不同的设计。 - Kerrek SB

16

不可能的。想一想这种情况。

int *ptr = new int(10);
int *ptrDup = ptr;

delete ptr;

但是ptrDup仍然指向ptr指向的内存位置,但是该位置已经不存在了。因此,对ptrDup进行引用会导致未定义的行为。但是有一个完全不同的概念——引用计数。


10
实际上,删除操作不会影响指针本身,因此指针仍然指向相同的内存地址,并对其进行解引用也将导致未定义行为... - Aconcagua
delete 会使 指针 本身失效,if (ptr != nullptr)if (ptrDup != nullptr)delete 后都是未定义的行为。可以通过在 delete 后重新初始化它们来使它们再次有效,如 ptr = nullptr; ptrDup = nullptr; - Eljay

10

在所有含义上,很难确定指针是否“有效”。

当然,您可以尝试引用指针(*ptr = x;x = *ptr)。如果代码没有崩溃,则指针指向有效的内存。如果它崩溃了,显然指针无效。不幸的是,这种方法有点像通过将枪对准头部来检查枪膛是否装弹,这并不是最聪明的选择...不幸的是,在指针方面,没有“检查弹膛是否装弹”的方法,因此没有真正好的方法来确定指针是否有效,除了“如果它不会导致硬件故障,它就是有效的”。

请注意,这通常只告诉您“指针指向您可以访问的一些内存”,而并不意味着指针“对于您想要的内容是正确的”(例如,它指向正确类型)。当然,它也无法告诉你指针是否指向“过时的数据”(也就是说,当指针曾经是有效的,但现在它是用于其他用途的内存)。

不幸的是,在现代系统中,有232 或 264 [实际上是248] 个可能有效的内存地址,几乎无法知道哪些地址是有效的,哪些是无效的。即使在操作系统内部,操作系统确定它是否可以写入您要求它写入的内存的方式是“尝试写入并查看发生了什么”。对于操作系统而言,这很好解决,因为它可以小心地处理“这可能出问题,并且如果出了问题,我将继续处理错误恢复代码”。操作系统必须处理这个问题,因为它必须接受a)程序员会犯错,b)某些人实际上编写恶意代码来尝试破坏操作系统。

应用程序“确保指针有效”的方法是程序员编写小心谨慎的代码,注意存储在指针中的内容、释放这些指针的方式,只使用存储了有效值的指针。你不应该最终“必须检查指针是否有效”——那样做就是“错误的”。
(当你在一个系统上工作一段时间,并在调试器中读取指针值时,你会逐渐认识到“好的”和“坏的”指针——但那只是因为你学会了通常情况下好指针与坏指针的外观。要编写代码来识别这种情况几乎是不可能的——特别是如果系统正在分配大量内存,以便使用大部分可用空间。)
当然,在C++中,有智能指针、向量和其他各种工具,这意味着很多时候你甚至不必费心处理指针。但是,理解如何使用指针以及指针的工作原理仍然是一件好事。

1
如果您取消引用指向非T类型对象的指针,行为是未定义的 - 因此您的开头陈述是错误的 - 它可能不会崩溃。 - Andrew Tomazos
@user1131467:好的,我已经添加了这样一条声明。我的主要观点实际上是,“如果你有一个坏指针(无效的,而不是指向错误类型),那么很难在不崩溃的情况下找出它”。 - Mats Petersson
2
我并不认为这个答案的前几段对帖子有任何有用的补充,特别是问题是关于指针是否指向“有效对象”,而不是“有效内存”。无效指针通常是一个指针,在用户调用delete之前是有效的,在这种情况下,它可能“看起来”正确,并且似乎工作正常,但实际上返回的是僵尸垃圾,它恰好位于应用程序的有效内存范围内。因此,考虑到所有这些陷阱,也许最好只说“不”,然后介绍如何避免检查原始指针的有效性? - Agentlien
@Agentlien 好的,我已经添加了一个“第一句”来表示“不行”。 - Mats Petersson
这对我来说已经足够了改进。+1 - Agentlien

4
如果一个指针被设置为nullptr,那就意味着它没有指向任何对象,而是被赋予了一个“默认”值。有可能这个指针既没有被分配给nullptr,同时也没有被分配给一个有效的对象,但在这种情况下无法确定。例如:
使用nullptr
int *ptr = nullptr;

// check if pointer is unassigned to an object
if (ptr == nullptr) ptr = new int{0};

没有 nullptr:
int *ptr;

// using ptr while uninitialized is Undefined Behavior!
if (ptr != &some_object)

2

如其他答案中所述,使用形式为SomeObject* somePointer的裸指针是不可能实现这一点的。然而,c++11引入了一组新的动态内存管理和智能指针。使用智能指针可以检测资源是否可用。例如,以下示例:

std::weak_ptr<int> w; // Our pointer to a resource.
{
    std::shared_pointer<int> s = std::make_shared<int>(5); // The resource.

    w = s;       // We can set the weak pointer to the shared pointer.

    auto s2 = w; // Here we can promote the weak pointer to a shared  pointer to control 
                 // the resource.

    *s2 = 6;     // Here we can use the resource.

}                // Here the resource is destroyed.

auto s2 = w;     // Here we will fail to get the resource because it has been destroyed. We
                 // have successfully used smart pointers to detect if the resource exists.

阅读更多关于 std::shared_ptrstd::weak_ptr 的内容,以获取更多示例。在c++11之前,类似的智能指针类型也可以在boost中找到。


智能指针在C++11中(大大)改进了,但它们早在很久以前就被引入了... - Ben Voigt
1
@BenVoigt 我会加上"new"这个词,但我不想在这里讨论auto_ptr,因为我需要写一整页来讲述为什么不要使用它。我也会添加一条关于boost的说明。 - Fantastic Mr Fox

-1

C++规范没有提供任何支持来确定指针是否有效。最好使用智能指针,因为您很少会误用它们(它们具有各种保护措施,允许正确操作)。

然而,各个公司开发了库和工具来添加代码以检查每个内存访问,如果其中一个无效,则会收到中断。

对于g++,我使用sanitizer选项,如下所示:

g++ -fsanitize=address -fsanitize=enum -fsanitize=unreachable ...

第一种方法将保护您的内存访问,以至于尝试使用错误的指针将被检测到并出现SEGV。它使用MMU来保护您的内存,因此是硬件驱动的。它会减慢您的代码,但仍然非常快。在这种模式下需要注意的一件事是,二进制文件分配了2Tb的虚拟内存。除非您有很多RAM,否则不要同时运行太多这样的二进制文件。

顺便说一句:部分代码来自Google,第一个实现是在clang中完成的。

在Linux下的直接C/C++中,您可以测试指针是否在您的进程内。但是,对于大内存支持,这将失败,您还必须考虑堆栈。起始指针类似于0x400000。堆的结束地址可以使用sbrk()确定。因此,您的堆指针应该在这两个边界之间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接