【问题标题】:Is it safe to change the dereference of an out-of-bounds pointer in C++?在 C++ 中更改越界指针的取消引用是否安全?
【发布时间】:2017-08-18 16:21:38
【问题描述】:

假设我有一个错误的指针,我递增它以查看下一个内存位置中的内容。我的代码运行良好,没有任何错误或问题。我只是打印出这些内存位置的内容。我在屏幕上看到了输出,其中一些内存位置的值为 0,其他内存位置包含一些似乎以模式排列的大负数或正数。但是,如果我尝试更改或覆盖这些内存位置的内容怎么办?它们代表什么?什么样的数据可能存储在这些内存位置,如果这些内存位置有足够多的改变,是否可能破坏操作系统?

  #include <iostream>
  using std::cout;
  using std::endl;
  int main() {
    int num1 = 5;
    int* bad_ptr = &num1;
    cout << "Address of num1: " << &num1 << endl;
    cout << "Dereference bad pointer: " << *bad_ptr << endl;

    // The bad pointer acesses 500 memory addresses
    for (int i = 0; i < 500; i++) {
      bad_ptr++;
      cout << "Dereference bad pointer: " << *bad_ptr << endl;
      // What if I try to change it?
      // *bad_ptr = 1;
    }
    return 0;
  }

【问题讨论】:

  • 未定义的行为是未定义的。您可以弄清楚您的特定编译器版本在特定时间对特定操作系统版本的特定优化级别和标志做了什么,但您可能不会学到任何有意义的东西。
  • “如果足够多的这些内存位置被更改,是否有可能破坏操作系统”取决于操作系统、程序类型和硬件架构。现代 CPU 具有防止一个进程损坏其他进程的保护措施,您的操作系统可能会利用这一点。但是,在内核驱动程序中执行此操作可能会导致错误。在具有更简单的内存管理模型的微控制器中执行此操作,该模型不会将操作系统与应用程序隔离,是的,操作系统可能会被踩踏。
  • 编译器假定未定义的行为永远不会在有效程序中发生。因此它可以优化包含 ub 的部分远离程序。或者它可能不会,或者它可能会打印一个零(这可能不是实际存储在内存位置的内容)或任何其他感觉的值
  • FWIW 回到旧的 MSDOS 时代,写入不属于您的内存可能会导致系统重新启动。
  • @PaulMcKenzie 在某些微控制器上,您可以覆盖 CPU 时钟倍频器并使其过热并着火

标签: c++ pointers memory shared-memory


【解决方案1】:

TL;DR这是未定义的行为,不应这样做。

更实际的答案是它在很大程度上取决于地址。在示例中,您正在获取int 的地址,该地址位于stack 上。如果继续递增指针,则基本上是在查看堆栈内存。如果您要使用来自对 malloc() 的调用的指针执行相同的操作,您将查看整个堆中的内存。

您关于通过更改值来影响操作系统稳定性的问题,除非您在内核空间中,否则答案是否定的。但是,如果您在内核空间中运行的驱动程序中执行此操作,则可能会使操作系统崩溃。

这个答案绝对不是详尽无遗的,而且令人难以置信的手摇,因为你的问题有很多细微差别,所以我建议你在开始时参考 TL;DR。我建议搜索和阅读基本的计算机架构。

【讨论】:

    【解决方案2】:

    引用错误的指针是未定义的。但是,您正在做的事情很可能不会偶然导致错误。

    在大多数 C++ 实现中,您的 num1 将分配在堆栈上。在大多数计算机系统中,堆栈向低地址增长。您递增指针是将您自己移回堆栈到调用函数的堆栈帧中(即所有在 main 之前进行初始化的函数)。

    如果你让你的循环限制足够大,最终你会看到一个问题。

    但是如果我尝试更改或覆盖这些内存位置的内容怎么办?它们代表什么?什么样的数据可能存储在这些内存位置,如果这些内存位置有足够多的改变,是否可能破坏操作系统?

    这都是系统特定的,但一般来说,当你调用一个函数时,你会在堆栈上创建一个 CALL FRAME。调用帧包括函数的参数、函数调用返回后要执行的下一条指令的地址以及保存的寄存器。调用帧包括被调用函数返回时恢复调用函数所需的所有信息。

    通常有一个硬件寄存器指向当前调用帧(一个帧指针)。

    在调用帧之间,堆栈包含当前执行函数的局部变量。

    当函数返回时,它使用帧指针寄存器来定位调用帧。然后恢复调用帧中的数据(包括之前保存的帧指针值),继续执行调用函数。

    如果您要覆盖指针的内容,那么您可能会遇到这种情况。您的调用函数可能会看到它们的变量发生变化。您的程序可能会崩溃。任何事情都有可能发生。

    有些函数会以您的方式与调用框架混淆。这需要准确了解调用帧的布局和寄存器使用情况。

    例如,有一个常见的库函数 alloca() 与 malloc() 类似,只是它在堆栈上分配内存,以便在当前函数返回时自动释放。实现 alloca 需要弄乱调用它的函数的状态。这需要了解调用框架的结构。

    如果你对这些值不屑一顾,你也不会破坏操作系统。你只会伤害自己。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-05-21
      • 2015-11-13
      • 1970-01-01
      • 1970-01-01
      • 2011-10-26
      • 1970-01-01
      • 2021-02-25
      相关资源
      最近更新 更多