【问题标题】:Why do dangling pointer deferences crash the program为什么悬空指针引用会使程序崩溃
【发布时间】:2025-12-03 21:25:01
【问题描述】:

我多次听说悬空指针取消引用会导致程序崩溃,但我不知道为什么。谁能给我解释一下?

据我了解,在 C/C++ 中,当我们通过指针 p 释放内存对象 o 后,p 变为指向语义上无效的内存地址 addr 的悬空指针。也许稍后addr 会被内存分配器重新分配给另一个内存对象。不管这种情况发生与否,操作系统都会认为addr仍然是一个合法的访问地址,因为内存分配器只向操作系统请求内存,而不会将内存还给操作系统。所以根本不应该有崩溃。谁能告诉我我的理解哪里错了?

【问题讨论】:

  • 您不应该抱怨崩溃。如果程序在这些条件下崩溃,情况会更糟。
  • 如果释放的内存随后被分配给包含指针的对象怎么办?现在这些指针将被(基本上)随机数据覆盖。下一次那些被覆盖的指针被取消引用......噗!
  • @Galik,我知道它可能会因为数据不一致而间接导致程序崩溃。但是我想知道当悬空指针被取消引用时程序如何立即崩溃,机制是什么?
  • 根据我的经验,程序通常不会在访问已释放的已分配内存后立即崩溃。但是,理论上,虚拟内存表中内存映射到的页面可以返回给操作系统,并在硬件寄存器中标记为非法,从而导致硬件异常。
  • @Galik,您是否建议只有将内存返回给操作系统时才会发生崩溃?如果我们使用不返回内存的分配器,那么在 use-after-free 站点不会立即崩溃,对吧?

标签: c++ c pointers memory memory-management


【解决方案1】:

无论这种情况发生与否,操作系统都会认为 addr 仍然是可以访问的合法地址

不,事实并非如此,恰恰相反。一旦你free() 内存,无论它是否实际被释放,都被认为是非法访问(甚至尝试再次free())。

由于每一次非法内存访问都会导致调用undefined behavior,因此也是如此,副作用是分段错误。

【讨论】:

  • 操作系统是否在某处注册了这些信息?类似于“无效的地址列表”?
  • @HumamHelfawi 这由内存分配器自己处理。
  • 哪个是操作系统的一部分,对吗?因为 C++ 是一种没有框架或虚拟机的本地语言?
  • @HumamHelfawi 如果您的意思是询问它是否会阻止您访问,它不会。它只会调用 UB。
  • 感谢您的回答。但我还是很困惑。谁在引用悬空指针、操作系统或内存分配器时使程序崩溃?我认为您是在建议内存分配器对此负责。但是内存分配器如何在每次取消引用指针时插入检查呢?
【解决方案2】:

抛开关于未定义行为的语言规则,释放内存很可能会将内存返回给操作系统。直接在调用 free 时或稍后在操作系统本身内存不足并要求应用程序返回未使用的块时。

当您不小心覆盖其他数据(例如数组长度或函数的返回地址)时,悬空指针也可能间接导致崩溃

【讨论】:

    【解决方案3】:

    悬空指针可以指向任何东西,有时程序可能会继续 因为它得到的值似乎是正确的,所以这可能会传播多次 直到出现段错误或更糟。

    foo *bar = malloc(sizeof(foo));
    void * dangling = &bar;
    free(bar);
    

    悬空现在指向什么?或者返回一个堆栈分配的结构作为 指针即

    char * foo() {
      char string[10] = "Boom";
      return string;
    }   
    

    基本上程序没有办法检测到正在使用的项目是有效的 在使用之前,有时即使使用它仍然无法检测到它。检测可以按严重程度按以下顺序发生...

    1. 编译器发现双重空闲或您正在返回对堆栈分配对象的引用。或者你试图释放一个它从未实际分配过的对象。

    2. 操作系统发现您正试图访问分配在进程地址空间之外的内存,即访问冲突/分段错误/总线错误等。

    3. 您的程序开始出现异常行为并最终崩溃,或者有人重新启动您的应用程序或内存不足等。

    在我的情况下,您的分配器(即 malloc)可能会从区域分配内存,即具有起始和结束地址的预分配内存块。任何释放这些范围之外的区域的请求都是错误的,在这种情况下您可能会遇到分段错误。这是分配器可以检测到的一个错误,它无法检测到您已经引用了随后被释放的内存区域,即现在是垃圾。然后你尝试使用垃圾和混乱随之而来。

    【讨论】:

    • 感谢您的回复,哈利。在您的最后一句话中,您说“有时即使使用它仍然无法检测到它”,但其他时候呢?谁检测到它,谁使程序崩溃?内存分配器,或者操作系统,还是别的什么?
    • 谢谢。正如我在问题中所问的那样,该地址始终有效,因为内存分配器永远不会将内存返回给操作系统,因此内存访问始终在进程地址空间内。让我们不要谈论由于传播的数据损坏而导致的以后程序崩溃。当悬空指针被取消引用时,程序是否有可能立即崩溃?
    • @Hua,那你就错了。对于悬空引用,地址可能无效,即它可能指向可能范围之外的地址,即 10^50。这可能在系统调用中,在这种情况下操作系统将终止您的程序,因为它是无效的。
    • 可能我没有明确定义悬空指针。悬空指针遵循,我的意思是释放后使用。悬空指针中的内容应该是内存分配器分配的先前有效地址,而不是我们通过将 int 转换为指针来构成的任意地址。
    • 问题还是一样,指向的可能是垃圾。 Malloc 本身不是您程序的一部分,即它不是用来“检测”您可能在哪里做一些您不应该做的事情。双重释放是一个好的编译器可能会检测到的东西,它使用对您已释放的东西的引用,并且该地址现在已分配给其他难以想象的东西,因为我知道今天任何 C 编译器都不会这样做。
    【解决方案4】:

    不能保证分配器不会将内存返回给操作系统,也不能保证未定义行为的任​​何其他保证。

    据我了解,一个令您满意的答案应该包含一个代码示例,其中取消引用悬空指针会立即使程序崩溃。下面是一个在 Linux 上崩溃(此时此地)的示例:

    #include <iostream>
    
    int main() {
        int* arr = new int[1000000];
        delete[] arr;
        std::cout << *arr << std::endl;
    }
    

    DEMO

    【讨论】:

    • 非常感谢。这就是我要找的。所以我之前对分配器的理解是错误的。谢谢。