将指针解引用到任意地址会导致分段错误

Cyn*_*ode 1 c pointers undefined-behavior dereference

我已经为指针编写了一个简单的C代码。根据我的理解,Pointer是一个变量,其中包含另一个变量的地址。例如:

 int x = 25; // address - 1024
 int *ptr = &x;
 printf("%d", *ptr); // *ptr will give value at address of x i.e, 25 at 1024 address.
Run Code Online (Sandbox Code Playgroud)

但是,当我尝试下面的代码时,我得到了分段错误

#include "stdio.h"
int main()
{
    int *ptr = 25;
    printf("%d", *ptr);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

怎么了 为什么指针变量不能返回地址25的值?我不应该能够读取该地址上的字节吗?

dbu*_*ush 5

除非您在具有特定已知内存位置的嵌入式系统上运行,否则不能将任意值分配给期望能够成功取消引用的指针。

C标准的 6.5.3.2p4节规定了有关间接操作符的以下内容*

一元运算 *符表示间接。如果操作数指向一个函数,则结果为一个函数指示符;否则为0。如果它指向一个对象,则结果是一个指定该对象的左值。如果操作数的类型为“要键入的指针”,则结果的类型为“ type”。 如果已将无效值分配给指针,则一元运算*符的行为 未定义。

如上文所述,C标准仅允许指针指向已知对象或动态分配的内存(或NULL),而不能指向任意内存位置。一些实现可能在特定情况下允许这样做,但总的来说是不允许的。


And*_*zel 5

尽管根据 C 标准未定义程序的行为,但您的代码实际上是正确的,因为它完全按照您的意图进行。它尝试从内存地址 25 读取并打印该地址处的值。

但是,在大多数现代操作系统(例如 Windows 和 Linux)中,程序使用虚拟内存而不是物理内存。因此,您很可能尝试访问未映射到物理内存地址的虚拟内存地址。访问未映射的内存位置是非法的,并且会导致分段错误。

由于内存地址 0(在 C 中写为NULL)通常被保留以指定无效内存地址,因此大多数现代操作系统从不将虚拟内存地址的前几千字节映射到物理内存。这样,当取消引用无效的 NULL 指针时,就会发生分段错误(这很好,因为它更容易检测错误)。

因此,您可以合理地确定地址 25(非常接近地址 0)也永远不会映射到物理内存,因此如果您尝试访问该地址,将会导致分段错误。

但是,程序虚拟内存地址空间中的大多数其他地址很可能会出现相同的问题。由于操作系统会尽可能节省物理内存,因此它不会将不必要的虚拟内存地址空间映射到物理内存。因此,大多数情况下,尝试猜测有效的内存地址都会失败。

如果您想探索进程的虚拟地址空间以查找可以读取而不会发生分段错误的内存地址,则可以使用操作系统提供的相应 API。在 Windows 上,您可以使用该函数VirtualQuery。在 Linux 上,您可以读取伪文件系统/proc/self/maps。ISO C 标准本身不提供任何确定虚拟内存地址空间布局的方法,因为这是特定于操作系统的。

如果你想探索其他正在运行的进程的虚拟内存地址布局,那么你可以VirtualQueryEx在Windows上使用该函数并/proc/[pid]/maps在Linux上阅读。不过,由于其他进程有独立的虚拟内存地址空间,所以不能直接访问它们的内存,而必须在Windows上使用ReadProcessMemory和函数,在Linux上使用。WriteProcessMemory/proc/[pid]/mem

免责声明:当然,我不建议乱搞其他进程的内存,除非您确切知道自己在做什么。

然而,作为一名程序员,您通常不想探索虚拟内存地址空间。相反,您通常使用操作系统分配给程序的内存。如果你希望操作系统给你一些内存来使用,你可以随意读取和写入(即没有分段错误),那么你可以将一个大的字符(字节)数组声明为全局变量,例如char buffer[1024];. 将较大的数组声明为局部变量时要小心,因为这可能会导致堆栈溢出。或者,您可以要求操作系统动态分配内存,例如使用该malloc函数。