【问题标题】:C++ Is it possible to determine whether a pointer points to a valid object?C ++是否可以确定指针是否指向有效对象?
【发布时间】:2013-06-19 22:40:22
【问题描述】:

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

给定一个指针p,你能确定p是否指向一个有效的对象吗?如果是这样,怎么做?如果没有,为什么不呢?

【问题讨论】:

  • 请定义“有效”。
  • 正是智能指针的优点。
  • 如果您知道底层内存管理器是如何工作的(它是如何记账的),那么您也许可以弄清楚。但是,这将是非常特定于平台的。如果您自己管理内存,那么这应该不是问题。最佳做法仍然是坚持最佳实践(RAII、封装)。
  • @chris - 智能指针的优点。但 GIGO 仍然适用;您始终可以创建包含错误指针的智能指针。

标签: c++ pointers


【解决方案1】:

不,你不能。为什么?因为维护有关什么构成有效指针和什么不是有效指针的元数据会很昂贵,而且在 C++ 中,您不需要为不想要的东西付费。

而且您不想要检查指针是否有效,因为您知道指针来自哪里,或者因为它是代码的私有部分您可以控制,或者因为您在面向外部的合同中指定了它。

【讨论】:

  • 我认为它确实并不昂贵...使用现在作为标准库一部分的智能指针,开销非常小。在绝大多数用例中,收益远远超过成本。有关更多信息,请参阅相关帖子:stackoverflow.com/questions/22295665/…
  • @tjwrona1992:这似乎过于简单和不切实际。智能指针要么具有不同的语义,因此无法比较(unique_ptr、shared_ptr),要么它们具有相同的语义和相同的问题(observer_ptr)。另请注意,由于 ABI 选择不佳(它不在寄存器中返回),因此 unique_ptr 在用作返回类型时实际上比原始指针更昂贵。
  • 我并不是说他们没有成本。我要说的是,如果您不是在嵌入式环境中工作,并且没有为内存非常有限且时钟速度较慢的微处理器编写代码,那么与几乎所有用例中的收益相比,成本基本上可以忽略不计。
  • @tjwrona1992:除了有一些真实的大型代码库对 unique_ptr 不必要的性能损失感到遗憾之外(在一定程度上它会产生真正的美元价值成本),你还忽略了我的主要要点:智能指针不是替代品,因为它们具有不同的语义。例如,没有明显的智能指针可以实际用于与原始指针语义匹配的非所有权转移引用语义;这将需要不同的设计。
【解决方案2】:

不可能。想想这种情况。

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

delete ptr;

ptrDup 仍然指向ptr 指向的内存位置,该位置已不存在。因此,尊重ptrDup 会导致未定义的行为。但是引用计数是完全不同的概念。

【讨论】:

  • 实际上,delete 并没有触及 ptr 本身,所以 ptr 仍然指向同一个内存并且取消引用它也是 UB...
【解决方案3】:

实际上不可能查看指针是否在所有含义上都是“有效的”。

当然,您可以尝试取消引用指针(*ptr = x;x = *ptr)。如果您的代码没有崩溃,则指针指向有效内存。如果它崩溃了,显然,指针不好。不幸的是,这种方法有点像通过向你的头部射击来检查枪支是否上膛——这不是最聪明的......不幸的是,使用指针,没有“检查枪膛是否上膛”,所以除了“如果它不会导致硬件故障,它是有效的”之外,没有真正的好方法来确定 a 指针是否有效。

请注意,在大多数情况下,这只会真正告诉您“指针指向您可以访问的某些内存”。这并不意味着指针“对于你想要的它是正确的”(例如,它指向正确的类型)。而且它肯定不会告诉您指针是否指向“陈旧数据”(即,当指针有效时,但它现在是用于其他用途的内存)。

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

应用程序“确保指针有效”的方法是,程序员编写代码时要注意它在指针中存储的内容、如何释放这些指针,并且只使用其中存储了有效值的指针。你不应该最终“不得不检查指针是否有效”——那么你就是“做错了”。

(当您使用系统一段时间并在调试器中读取指针值时,您会在一段时间后识别“好”和“坏”指针 - 但这只是因为您了解了通常什么是好的指针vs. 一个坏指针看起来像。编写代码来识别这种情况几乎是不可能的 - 特别是如果系统分配了大量内存,因此它使用了大部分可用空间。)

当然,在 C++ 中,有智能指针、向量和各种其他工具,这意味着很多时候您甚至不必为指针操心。但是了解如何使用指针以及指针如何工作仍然是一件好事。

【讨论】:

  • 如果你取消一个指向 T 的指针,它不指向 T 类型的对象,行为是未定义的 - 所以你的开场白是假的 - 它可能不会崩溃。
  • @user1131467:好的,我已经为此添加了一个声明。我的主要观点是“如果你有一个错误的指针(无效,而不是指向错误的类型),很难在不崩溃的情况下找到它”。
  • 我不相信这个答案的前几段会为帖子添加任何有用的东西。特别是因为问题是关于指针是否指向“有效对象”,而不是“有效内存”。无效指针通常是在用户调用delete 之前有效的指针,在这种情况下,它可能“看起来”正确并且似乎工作但实际上返回的僵尸垃圾恰好位于应用程序的有效内存范围内。那么,有了所有这些陷阱,也许最好将其保留为“否”,然后如何避免检查原始指针的有效性?
  • @Agentlien 好的,我已经添加了“第一句话”来表示“不,你不能”。
  • 这对我的品味来说已经足够提升了。 +1
【解决方案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)

【讨论】:

    【解决方案5】:

    正如其他答案中所述,使用SomeObject* somePointer 形式的原始指针是不可能的。但是,c++11 引入了一组新的dynamic memory management and new smart pointers。使用智能指针,您可以检测资源是否仍然可用。例如以下:

    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 中(很多)更好,但它们在很久以前就被引入了......
    • @BenVoigt 我将添加新词,但我真的不想在这里谈论auto_ptr,因为我需要一个页面说明为什么不使用它。我也会添加一个关于 boost 的注释。
    【解决方案6】:

    C++ 规范没有为我们提供任何类型的支持来确定指针是否有效。最好的办法是使用智能指针,因为您不太可能误用它们(它们有各种保护措施允许正确操作)。

    然而,许多公司开发库和工具来添加代码来检查每个内存访问,如果一个无效,你会得到一个中断。

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

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

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

    附带说明:部分代码来自 Google,第一个实现是在 clang 中。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-23
      相关资源
      最近更新 更多