【问题标题】:Dereferencing pointer to arbitrary address gives Segmentation fault取消引用指向任意地址的指针会导致分段错误
【发布时间】:2023-03-26 06:57:01
【问题描述】:

我为指针编写了一个简单的 C 代码。根据我的理解,指针是一个变量,它保存另一个变量的地址。 例如:

 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.

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

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

这有什么问题?为什么指针变量不能返回地址 25 处的值?我不应该能够读取该地址的字节吗?

【问题讨论】:

  • ` int *ptr = 25;` 表示ptr 将指向地址25。它与你提到的其他数字有什么关系——我不知道。
  • 是的。 ptr 指向地址 25。为什么打印*ptr 会出现段错误。它必须在地址 25
  • 你第一次做对了:int x = 25; int *ptr = &x; printf("%d", *ptr); 完全没问题。 int *ptr = 25; printf("%d", *ptr); 是垃圾代码。
  • @Cynacode 你怎么知道地址 25 是有效的并且正确对齐以寻址 int?如果您认为您知道它是有效且正确对齐的,那么系统似乎不同意,因此出现分段错误。
  • @IanAbbott 同意你的看法。但是printf("%d", ptr); 将正确打印值25 这是怎么回事?

标签: c pointers undefined-behavior dereference


【解决方案1】:

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

C standard 的第 6.5.3.2p4 节对间接运算符 * 进行了以下说明:

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

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

【讨论】:

    【解决方案2】:

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

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

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

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

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

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

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

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

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

    【讨论】:

      【解决方案3】:

      您应该考虑编译器发出的所有警告。

      此声明

      int *ptr = 25;
      

      不正确。您正在尝试将整数分配给指针作为内存地址。因此在此声明中

      printf("%d", *ptr);
      

      有人试图访问不属于您的程序的地址为 25 的内存。

      你的意思是如下

      #include "stdio.h"
      
      int main( void )
      {
          int x = 25;
      
          int *ptr = &x;
      
          printf("%d", *ptr);
      
          return 0;
      }
      

      或者

      #include "stdio.h"
      #include <stdlib.h>
      
      
      int main( void )
      {
          int *ptr = malloc( sizeof( int ) );
      
          *ptr = 25;
      
          printf("%d", *ptr);
      
          free( ptr );
      
          return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-04
        • 2018-10-19
        • 1970-01-01
        • 2018-01-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多