我正在学习 C++ 并阅读 C++ Primer。有个问题想知道答案:
给定一个指针p,你能确定p是否指向一个有效的对象吗?如果是这样,怎么做?如果不是,为什么不呢?
不,你不能。为什么?因为维护有关什么构成有效指针和什么不是有效指针的元数据会很昂贵,而且在 C++ 中,您无需为不想要的内容付费。
而且您不想检查指针是否有效,因为您知道指针的来源,或者因为它是您控制的代码的私有部分,或者因为您在面向外部的合同中指定了它。
不可能。想想这个场景。
int *ptr = new int(10);
int *ptrDup = ptr;
delete ptr;
但ptrDup
仍然指向ptr
不再存在的内存位置。因此,deferencingptrDup
会导致未定义的行为。但是有引用计数,这是一个完全不同的概念。
真的不可能看到一个指针是否在所有含义上都是“有效的”。
当然,您可以尝试取消引用指针(*ptr = x;
或x = *ptr
)。如果您的代码没有崩溃,则指针指向有效内存。如果它崩溃了,显然,指针不好。不幸的是,这种方法有点像通过向你的头部射击来检查枪支是否装载 - 这不是最聪明的......不幸的是,使用指针,没有“检查腔室以查看它是否装载”,所以除了“如果它不会导致硬件故障,它是有效的”之外,没有真正的好方法来确定 a 指针是否有效。
请注意,在大多数情况下,这只会真正告诉您“指针指向您可以访问的某些内存”。这并不意味着指针“对于您想要的它是正确的”(例如,它指向正确的类型)。而且它肯定不会告诉您指针是否指向“陈旧数据”(即,当指针有效时,但它现在是用于其他用途的内存)。
不幸的是,在现代系统中有 2 32或 2 64 [实际上是 2 48 ] 可能有效的内存地址,几乎不可能知道哪些地址是有效的,哪些是无效的。即使在操作系统内部,操作系统确定它是否可以写入您要求它写入的内存的方式是“尝试写入它,看看会发生什么”。对于操作系统来说,这很好,因为它可以小心“这可能会出错,如果出错,我会在错误恢复代码中继续那里”。操作系统必须处理这个问题,因为它必须接受 a) 程序员犯错误,以及 b) 有些人实际上编写了恶意代码来尝试破坏操作系统。
应用程序“确保指针有效”的方法是程序员编写代码时要小心它在指针中存储的内容,如何释放这些指针,并且只使用其中存储了有效值的指针。你不应该最终“不得不检查指针是否有效”——那么你就是“做错了”。
(当您使用系统一段时间并在调试器中读取指针值时,您会在一段时间后识别“好”和“坏”指针 - 但这只是因为您了解通常情况下好的指针与坏指针看起来像。编写代码来识别这种情况几乎是不可能的 - 特别是如果系统分配了大量内存,因此它使用了大部分可用空间。)
当然,在 C++ 中,有智能指针、向量和各种其他工具,这意味着很多时候您甚至不必为指针操心。但是了解如何使用指针以及指针如何工作仍然是一件好事。
如果指针设置为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)
如其他答案所述,使用表单的原始指针是不可能的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
C++ 规范没有为我们提供任何类型的支持来确定指针是否有效。最好的方法是使用智能指针,因为您不太可能滥用它们(它们有各种保护措施允许正确操作)。
然而,许多公司开发了库和工具来添加代码来检查每个内存访问,如果一个无效,你会得到一个中断。
使用 g++,我使用如下的消毒剂选项:
g++ -fsanitize=address -fsanitize=enum -fsanitize=unreachable ...
第一个将保护您的内存访问到尝试使用错误指针将被 SEGV 检测到的点。它使用 MMU 来保护您的内存,因此它是硬件驱动的。它会减慢您的代码速度,但它仍然非常快。在这种模式下需要注意的一件事是,二进制文件分配了 2Tb 的虚拟内存。除非您有大量 RAM,否则您不希望同时运行太多此类二进制文件。
作为旁注:部分代码来自谷歌,第一个实现是铿锵的。
在 Linux 下的直接 C/C++ 中,您可以测试指针是否在您的进程中。但是,如果支持大内存,这将失败,您也必须考虑堆栈。起始指针类似于 0x400000。堆的结束地址可以使用来确定sbrk()
。所以你的堆指针应该在这两个边界之间。