【问题标题】:Can I access restricted memory space by pointers我可以通过指针访问受限的内存空间吗
【发布时间】:2021-04-27 04:25:06
【问题描述】:

所以当我了解指针时,我有了一个疯狂的想法: 如果我打印一个指针,它会给出内存中的地址,如果是这样,实际读取的是计算机内存的哪一部分?例如

#include <iostream>
using namespace std;
main()
{
   int a=1;
   int* ptr=&a;
   int i=0;
   while(1)
   {
      cout<<(ptr+i)<<"\t"<<*(ptr+i)<<"\n";
      i++;
   }
}

当我运行这个程序时(考虑到没有错误),它给了我来自&amp;a++ 的地址。所以它会遍历内存中的地址,并显示存储在那里的信息。所以我的问题是,正在读取内存的哪一部分?我是否可以访问每个内存位置(存储在每个内存位置的值)?我可以像这样删除(这听起来很愚蠢)我电脑上的文件吗?

ptr=nullptr;
while(1)
{
   *(ptr+i)=0;
   i++;
}

我不知道我的语法是否正确,尽管你明白了。我不敢在我的电脑上运行这个......

【问题讨论】:

  • 您不能访问不属于您的内存。在指针不再指向它所指向的对象/数组后,您也不能访问它。这段代码所做的任何事情都是未定义的行为。
  • 这取决于您的系统。在没有内存保护的古老或嵌入式系统上,这样做确实可能会覆盖其他程序或操作系统的一部分(通常不是文件,因为它们将在磁盘上而不是在内存中,尽管缓存在内存中的文件可能会损坏)。在现代台式机或服务器系统上,硬件内存保护不允许这种情况发生。
  • @NateEldredge:桌面操作系统可能会阻止程序访问进程不拥有的存储,但进程通常会拥有超出 C 或 C++ 语言构造可用于 C 或C++ 程序和实现通常不会捕获对此类存储的访问。
  • 取决于操作系统和硬件。在许多嵌入式系统上,访问未定义的地址没有问题。其他平台有硬件“围栏”以防止区域被访问。当您访问程序空间之外的地址时,某些操作系统可能会产生故障。它们是否产生异常再次取决于您的系统;没有通知要求。

标签: c++ pointers memory-management


【解决方案1】:

在运行 C 或 C++ 程序时,地址可分为以下几类:

  1. 那些已通过报告 const 限定对象地址的语言结构提供给 C 或 C++ 程序的那些。

  2. 那些已通过语言构造提供给 C 或 C++ 程序的对象,但不是从 const 限定的对象派生的。

  3. 空地址。

  4. 语言实现已被环境授予独占使用,但尚未通过语言构造提供给程序的那些。

  5. 语言实现一无所知的那些。

如果代码尝试使用除前三个类别之外的任何地址执行任何操作,尝试访问前两个类别之外的任何地址的存储,或尝试写入任何地址不属于第二类。

然而,设计为适合低级编程任务的实现将定义超出标准规定的行为。最值得注意的是,此类实现将“以环境的 [已记录] 方式”处理类型 #5 的访问,其行为将在环境记录时以任何方式记录下来。

例如,如果一个人正在运行一个以 Commodore 64 为目标的 C 实现,并且一个人执行*(char volatile*)0xD020 = 2;,这将导致屏幕边框变为红色,因为 Commodore 64 记录了写入的效果那个特定的地址。 C 编译器对“屏幕”、“边框”或“红色”等概念一无所知,但它不需要知道这些东西。它只会对地址 0xD020 执行写入操作,硬件将通过更改颜色控制锁存器中的值来响应,这些锁存器在光栅扫描处于边界区域时进行采样。此类代码仅在定义将值 2 存储到地址 0xD020 的效果的平台上才有意义,但旨在用于低级编程的实现不应期望知道,因此也不应该关心此类构造何时会或何时会没有用,因为程序员通常比编译器编写者更了解目标环境。

顺便说一句,UB 可以擦除存储介质的想法可能与以下事实有关:在 Apple //c 等某些机器上,或在插槽 6(通常位置)中带有软盘控制器的任何 Apple Ii 系列机器上,在软盘驱动器运行时访问(甚至读取)地址 0xC0EF(包括软盘驱动器访问的第一秒左右的任何时间)将导致驱动器开始覆盖当前磁道上的数据,直到下一次访问 0xC0EE。尽管大多数读取在大多数平台上都不会产生副作用,但杂散读取可能造成灾难性后果的平台不仅仅是理论上的。

【讨论】:

    【解决方案2】:

    如果我打印一个指针,它会给出内存中的地址,如果是,那么实际读取的是计算机内存的哪一部分?

    解释可执行文件如何工作的最简单方法是,首先将可执行文件从 ROM(HHD 和/或 SSD)加载到 RAM,然后由 CPU 执行。当您引用一个有效变量的指针时,它显示的是该变量在 RAM 中的内存地址。

    当我运行这个程序时(考虑到没有错误),它给了我来自 &a ++ 的地址。所以它会遍历内存中的地址,并显示存储在那里的信息。所以我的问题是,内存的哪一部分被读取了?

    它显示了可执行文件可能不拥有的内存位置。你不应该访问你不允许的内存!通常它会触发运行时错误说明:Access Violation Reading Location 0x...

    我是否可以访问每个内存位置(存储在每个内存位置的值)?

    不,你不能也不应该。那是因为操作系统不允许你读取你不拥有的内存,这是一个安全威胁。如果要访问一块内存,请先分配它。

    我可以这样删除(这听起来很愚蠢)我电脑上的文件吗

    不,文件存储在 ROM 中,而您在该指针中指向的内容存储在 RAM 中。

    我不敢在我的电脑上运行这个...

    是的,您不应该也不能这样做,因为操作系统可能会阻止您这样做(某些操作系统可能不会严格执行它,例如:嵌入式系统)。

    【讨论】:

    • 事实上,在任何远程现代的东西上,指针都不会指向你不拥有的内存。指针不指向任何地方,因为页表查找表明该页不存在。
    • 等一下……所以如果指针指向 RAM 中的地址,那么我的操作系统就不会受到威胁。如果我读取 RAM,我不会访问 HD,所以我不会使用永久存储进行操作
    • @Mr.Stark135 因此,如果指针指向 RAM 中的地址,那么我的操作系统不会受到威胁可能不会影响 RAM,但它可能会干扰其他应用程序。现代操作系统无论如何都会阻止您这样做。
    【解决方案3】:

    不,如果你能做到这一点,那么“限制”就没有用了。

    在过去——想想:MS-DOS 和 Windows 3.1 时代——你可以做到这一点。它会覆盖属于其他程序的内存,并可能导致计算机崩溃。

    现代操作系统使用一种称为虚拟内存的技术,操作系统控制您的地址!每当您访问指针 0x12345678 时,CPU 都会在 页表 中查找有关 0x12345 的信息。

    如果那是属于您的程序的页面,它会将页面地址更改为页表所说的 - 所以现在它是 0xabcde678 - 并从该 RAM 芯片中获取数据。这允许操作系统在您的程序没有注意到的情况下移动页面。前一时刻 0x12345678 表示 0xabcde678,下一时刻表示 0x54321678,你分不出来。

    如果它不是属于您的程序的页面,则页表中不会有新地址。在这种情况下,CPU 引发 页面错误异常 - 它调用操作系统中处理页面错误的特定函数。这个函数会看到没有很好的缺页原因(因为有一些很好的原因)。它会结束您的程序并弹出一个消息框,提示“此程序遇到问题,需要关闭”。

    【讨论】:

      【解决方案4】:

      代码是对程序编译后应该做什么的抽象描述。此抽象描述的基础是标准中指定的 C++ 语言。标准中没有任何内容指定获取任意指针、递增它然后取消引用它的含义(除非所有这些都发生在数组内)。官方术语是:未定义的行为。如果你用未定义的行为编译代码,任何事情都可能发生。这有点像强迫某人将“foofoo moomoo”从英语翻译成另一种语言。

      在存在未定义行为的情况下,我们可以使用我们对 C++ 定义明确的部分的了解,并尝试推断当您编译和运行具有未定义行为的代码时会发生什么。然而,这是徒劳的,因为有时编译器可以检测到 UB 并采取相应的行动。如果您要求编译器编译您的代码,则在生成的可执行文件中根本不需要任何指针增量(因为您的指针增量和随后的取消引用是未定义的)。

      如果您仍然想知道生成的可执行文件实际上做了什么,您需要研究编译器的输出,例如汇编。但是,当代码具有未定义的行为时,根据编译器、其版本和设置的不同,结果会有很大差异,请不要感到惊讶。

      【讨论】:

      • 我喜欢翻译类比,但 463035818 仍然不是素数...
      • "foofoo moomoo" 在德语中是 "blubblub muhmuh"
      • @Joshua 不,它是“Ich werde diese Schallplatte nicht kaufen。Sie ist zerkratzt。”绝对!
      • @AyxanHaqverdili 我喜欢这个想法,但我有点失望,因为我没有设法从一开始就包含一个 monty python 参考
      【解决方案5】:

      在一般意义上,动态分配的空间通常放置在称为堆或空闲存储的程序段中。您必须使用 new 关键字让计算机从该堆空间分配内存。

      当您用完堆空间时,例如当您继续动态分配新指针但不删除未使用的指针时,可能会发生这样的错误std::bad_alloc

      另一方面,如果您尝试访问未动态分配的内存。 Segmentation Fault 几乎肯定会发生。 一个非常简单的示例是当您尝试读取超出范围的数组元素时。由于 C++ 没有边界检查。您可以轻松冒险进入危险区域并禁止非法内存访问。在您的情况下,尝试访问您分配的内存旁边但未分配的内存会导致分段错误。当然,你绝对可以打印内存。但是访问内存并实际更改其内容是完全不同的事情。

      【讨论】:

        【解决方案6】:

        我在 Visual Studio Community 2019 中测试了你的程序。打印了许多内存位置,但几秒钟后,抛出异常:读取访问冲突。 程序中的所有变量都是在堆栈上创建的。循环的第一次迭代显示变量“a”(值 1)的内容,它位于堆栈上,但下一次迭代显示也属于程序堆栈的内存内容。最后,尝试读取堆栈外的内存位置,因此抛出异常。 在 MS-DOS 时代,尝试读取程序堆栈之外的内存不会有任何问题,因为操作系统不保护内存位置。

        当我尝试使用 '*(ptr + i) = 0;' 写入内存位置时而不是阅读它们,抛出的是写访问冲突异常。在 MS-DOS 时代,这会导致覆盖程序堆栈之外的内存位置,这会影响其他正在运行的程序,可能计算机会挂起,您将不得不重新启动它,这在出错时很常见是在使用指针时制作的。

        【讨论】:

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