【问题标题】:why am I not gettting Segmentation error?为什么我没有收到分段错误?
【发布时间】:2019-01-07 06:25:44
【问题描述】:

我有

x=(int *)malloc(sizeof(int)*(1));

但我仍然可以阅读 x[20]x[4]

我如何能够访问这些值?我不应该在访问这些内存时遇到分段错误吗?

【问题讨论】:

  • 当您的程序有未定义的行为时,不要期望任何可预测的事情。
  • 用户空间中没有内存防护。您正在访问的是由其他一些用户空间程序分配的内存。它不会导致你的程序直接崩溃,但你会得到意想不到的结果。
  • @SoumyaKanti 这不是任何现代操作系统的工作方式......不同的用户空间程序生活在不同的虚拟地址空间中,所以他绝对不会从另一个用户空间程序访问内存。更有可能的是,他只是在访问他的进程堆(或属于堆数据结构的数据)的下一个(可能已分配,可能未分配)块。
  • 作为一个实用的解释为什么它经常不会出错,大多数当前的主流硬件架构只能对整个page 内存强制执行访问权限。如果一个进程对页面的任何部分具有写访问权,那么它对整个页面具有写访问权,并且页面可能类似于 4096 字节,因此给出的示例可能不会跨越页面边界。但那是计算机 - 就 C 标准所说的而言,你正在做的事情是未定义的,从你的显示器中飞驰而出的独角兽并不比任何其他结果更非法。
  • @SoumyaKanti:关键是,如果您要提供详细信息,则必须提供正确详细信息,或者根本不提供;我见过很多人因为“善意的谎言”被“帮助”分发而头脑中出现奇怪的困惑。对于初学者来说,好的解释可能会在困难的方面进行简化,但即使在专业人士的眼中也仍然完全正确。至于在进程之间没有内存隔离的操作系统,那是非常古老或专门的东西,鉴于他正在谈论段错误,很可能不是 OP 使用的。

标签: c malloc dynamic-memory-allocation


【解决方案1】:

基本前提是 Sourav Ghosh 的回答:访问从 malloc 返回的内存超出您要求的大小是未定义的行为,因此允许符合要求的实现几乎可以做任何事情,包括愉快地返回奇异值。

但考虑到“正常”机器(gcc/MSVC/clang、Linux/Windows/macOS、x86/ARM)上的主流操作系统上的“正常”实现,为什么有时会出现分段错误(或访问冲突),以及有时不会?

几乎每个“常规”C 实现在通过指针读取/写入时都不执行任何类型的内存检查1;这些加载/存储通常直接转换为相应的机器代码,该机器代码在给定位置访问内存,而无需过多考虑“抽象 C 机器”对象的大小。

但是,在这些机器上,CPU 并不直接访问 PC 的物理内存 (RAM),而是引入了转换层 (MMU)2;每当你的程序试图访问一个地址时,MMU 会检查那里是否映射了任何东西,以及你的进程是否有权限写入那里。万一这些检查中的任何一个失败3,您会遇到分段错误并且您的进程会被终止。这就是为什么未初始化和NULL 指针值通常会给出很好的段错误的原因:虚拟地址空间开头的一些内存被保留未映射,只是为了发现NULL 取消引用,通常如果你将飞镖随机扔到 32 位地址空间(或者甚至更好,64 位的)你最有可能找到从未映射到任何东西的内存区域。

尽管如此,MMU 无法捕获所有内存错误,原因有很多。

首先,与大多数“普通”分配相比,内存映射的粒度相当粗糙;在 PC 上的内存页(可以映射并具有保护属性的最小内存单元)的大小通常为 4 KB。这里当然需要权衡:非常小的页面本身需要大量内存(因为每个页面都有一个目标物理地址和保护属性,并且必须存储在某个地方)并减慢 MMU 操作 3。因此,如果您在“逻辑”边界之外访问内存但仍在同一内存页面内,则 MMU 无法帮助您:就硬件而言,您仍在访问有效内存。

此外,即使您超出了分配的最后一页,就硬件而言,后面的页面也可能是“有效的”;实际上,这对于您从所谓的堆(malloc 和朋友)获得的内存来说是很常见的。

这是因为malloc,对于较小的分配,不会向操作系统询问“新”内存块(理论上可以在分配时在两端保留一个保护页);相反,C 运行时中的分配器以大的顺序块向操作系统请求内存,并在逻辑上将它们划分为较小的区域(通常保存在某种链接列表中),这些区域在 malloc 上分发并由 @987654326 返回@。

现在,当您在程序中超出所请求内存的边界时,您可能不会收到任何错误:

  • 您使用的内存块不在页面边界附近,因此您的越界读取不会触发访问冲突;

  • 即使它在页面的末尾,后面的页面仍然被映射,因为它仍然属于堆;它可能是已分配给进程的其他代码的内存(因此您正在读取代码中某些不相关部分的数据),也可能是空闲内存区域(因此您正在读取前一个进程留下的任何垃圾)块的所有者freed 它),或分配器用来保存其簿记数据的区域(因此您正在读取此类数据的一部分)。

    在所有这些情况下,除了“空闲块”之外,即使你要在那里写,你也不会遇到分段错误,但你可能会破坏不相关的数据或堆的数据结构(这通常会导致稍后崩溃,因为分配器发现其数据不一致)。


备注

  1. 尽管现代编译器提供了特殊的检测构建来捕获其中一些错误;尤其是 gcc 和 clang,提供了所谓的“地址清理器”。
  2. 这允许引入透明分页(在物理内存可用性低的情况下交换到未主动使用的磁盘内存区域),最重要的是,内存保护和地址空间分离(当用户模式进程正在运行时,它“看到”一个完整的虚拟地址空间,其中只包含他的东西,而没有来自其他进程或内核的任何东西)。
  3. 并且通知进程正在尝试访问已换出的内存并不是操作系统故意设置的故障。
  4. 鉴于每次访问内存都需要经过MMU,映射必须非常快,所以最常用的页面映射保存在缓存中;如果您使页面非常小并且缓存可以容纳同样多的条目,那么缓存覆盖的内存范围实际上会更小。

【讨论】:

    【解决方案2】:

    不,访问无效内存是undefined behaviorsegmantation fault 是 UB 的众多副作用之一。无法保证。

    也就是说,

    • 在使用返回的指针之前,始终通过检查返回的指针与 NULL 来检查 malloc() 是否成功。
    • 请看这个:Do I cast the result of malloc?

    【讨论】:

    • 那么当我访问这些内存时我得到了什么值?
    • @v1234 关键是,你甚至不能访问那些记忆。因此问题本身的前提是错误的。
    • 查看我对您问题的评论。
    猜你喜欢
    • 2012-10-24
    • 2012-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-06
    • 1970-01-01
    • 2022-01-21
    相关资源
    最近更新 更多