我正在学习C++并阅读《C++ Primer》。我有一个问题想要知道答案:
给定一个指针p,你能否确定p是否指向有效的对象?如果可以,那么如何确定?如果不行,为什么不行?
我正在学习C++并阅读《C++ Primer》。我有一个问题想要知道答案:
给定一个指针p,你能否确定p是否指向有效的对象?如果可以,那么如何确定?如果不行,为什么不行?
不行,为什么呢?因为维护关于哪些指针是有效的、哪些不是有效的元数据会很昂贵,在C++中,只要你不需要的东西就不用付出代价。
而且你不想检查指针是否有效,因为你知道指针来自哪里,无论是因为它是你控制的代码的私有部分,还是因为你在外部合约中指定了它。
不可能的。想一想这种情况。
int *ptr = new int(10);
int *ptrDup = ptr;
delete ptr;
但是ptrDup
仍然指向ptr
指向的内存位置,但是该位置已经不存在了。因此,对ptrDup
进行引用会导致未定义的行为。但是有一个完全不同的概念——引用计数。
delete
会使 指针 本身失效,if (ptr != nullptr)
和 if (ptrDup != nullptr)
在 delete
后都是未定义的行为。可以通过在 delete
后重新初始化它们来使它们再次有效,如 ptr = nullptr; ptrDup = nullptr;
。 - Eljay在所有含义上,很难确定指针是否“有效”。
当然,您可以尝试引用指针(*ptr = x;
或x = *ptr
)。如果代码没有崩溃,则指针指向有效的内存。如果它崩溃了,显然指针无效。不幸的是,这种方法有点像通过将枪对准头部来检查枪膛是否装弹,这并不是最聪明的选择...不幸的是,在指针方面,没有“检查弹膛是否装弹”的方法,因此没有真正好的方法来确定指针是否有效,除了“如果它不会导致硬件故障,它就是有效的”。
请注意,这通常只告诉您“指针指向您可以访问的一些内存”,而并不意味着指针“对于您想要的内容是正确的”(例如,它指向正确类型)。当然,它也无法告诉你指针是否指向“过时的数据”(也就是说,当指针曾经是有效的,但现在它是用于其他用途的内存)。
不幸的是,在现代系统中,有232 或 264 [实际上是248] 个可能有效的内存地址,几乎无法知道哪些地址是有效的,哪些是无效的。即使在操作系统内部,操作系统确定它是否可以写入您要求它写入的内存的方式是“尝试写入并查看发生了什么”。对于操作系统而言,这很好解决,因为它可以小心地处理“这可能出问题,并且如果出了问题,我将继续处理错误恢复代码”。操作系统必须处理这个问题,因为它必须接受a)程序员会犯错,b)某些人实际上编写恶意代码来尝试破坏操作系统。
应用程序“确保指针有效”的方法是程序员编写小心谨慎的代码,注意存储在指针中的内容、释放这些指针的方式,只使用存储了有效值的指针。你不应该最终“必须检查指针是否有效”——那样做就是“错误的”。delete
之前是有效的,在这种情况下,它可能“看起来”正确,并且似乎工作正常,但实际上返回的是僵尸垃圾,它恰好位于应用程序的有效内存范围内。因此,考虑到所有这些陷阱,也许最好只说“不”,然后介绍如何避免检查原始指针的有效性? - Agentliennullptr
,那就意味着它没有指向任何对象,而是被赋予了一个“默认”值。有可能这个指针既没有被分配给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)
如其他答案中所述,使用形式为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_ptr 和 std::weak_ptr 的内容,以获取更多示例。在c++11
之前,类似的智能指针类型也可以在boost
中找到。
auto_ptr
,因为我需要写一整页来讲述为什么不要使用它。我也会添加一条关于boost的说明。 - Fantastic Mr FoxC++规范没有提供任何支持来确定指针是否有效。最好使用智能指针,因为您很少会误用它们(它们具有各种保护措施,允许正确操作)。
然而,各个公司开发了库和工具来添加代码以检查每个内存访问,如果其中一个无效,则会收到中断。
对于g++,我使用sanitizer选项,如下所示:
g++ -fsanitize=address -fsanitize=enum -fsanitize=unreachable ...
第一种方法将保护您的内存访问,以至于尝试使用错误的指针将被检测到并出现SEGV。它使用MMU来保护您的内存,因此是硬件驱动的。它会减慢您的代码,但仍然非常快。在这种模式下需要注意的一件事是,二进制文件分配了2Tb的虚拟内存。除非您有很多RAM,否则不要同时运行太多这样的二进制文件。
顺便说一句:部分代码来自Google,第一个实现是在clang中完成的。
在Linux下的直接C/C++中,您可以测试指针是否在您的进程内。但是,对于大内存支持,这将失败,您还必须考虑堆栈。起始指针类似于0x400000。堆的结束地址可以使用sbrk()
确定。因此,您的堆指针应该在这两个边界之间。