Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/57.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 为什么我没有得到分割错误?_C_Malloc_Dynamic Memory Allocation - Fatal编程技术网

C 为什么我没有得到分割错误?

C 为什么我没有得到分割错误?,c,malloc,dynamic-memory-allocation,C,Malloc,Dynamic Memory Allocation,我有 x=(int *)malloc(sizeof(int)*(1)); 但我仍然能够阅读x[20]或x[4] 我如何才能访问这些值?在访问这些内存时,我不应该出现分段错误吗?不,访问无效内存是,也是UB的许多副作用之一。这不是保证 也就是说 在使用返回的指针之前,请始终通过对照NULL检查返回的指针来检查malloc()是否成功 请看这个: 基本前提是Sourav Ghosh的答案:访问从malloc返回的超出您要求大小的内存是未定义的行为,因此一致性实现几乎可以做任何事情,包括愉快地返回

我有

x=(int *)malloc(sizeof(int)*(1));
但我仍然能够阅读
x[20]
x[4]


我如何才能访问这些值?在访问这些内存时,我不应该出现分段错误吗?

不,访问无效内存是,也是UB的许多副作用之一。这不是保证

也就是说

  • 在使用返回的指针之前,请始终通过对照
    NULL
    检查返回的指针来检查
    malloc()
    是否成功
  • 请看这个:

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

但是,如果主流操作系统在“普通”机器(gcc/MSVC/clang、Linux/Windows/macOS、x86/ARM)上实现“正常”,为什么有时会出现分段错误(或访问冲突),有时则不会

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

然而,在这些机器上,CPU并不直接访问PC的物理内存(RAM),而是引入了转换层(MMU);每当您的程序尝试访问某个地址时,MMU都会检查是否有任何内容映射到该地址,以及您的进程是否具有在该地址上写入的权限。如果这些检查中的任何一个失败3,您将得到一个分段错误,并且您的进程将被终止。这就是为什么未初始化和
NULL
指针值通常会给出很好的分段错误:虚拟地址空间开头的一些内存被保留为未映射的,只是为了发现
NULL
解引用,通常情况下,如果您随机向32位地址空间(甚至更好,是64位地址空间)抛出一个省道您最有可能找到从未映射到任何对象的内存区域

尽管如此,MMU还是无法捕获所有内存错误,原因有几个

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

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

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

现在,当您在程序中跳出请求内存的边界时,您可能不会得到任何错误,因为:

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

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

    在除“空闲块”之外的所有这些情况下,即使在其中写入,也不会出现分段错误,但可能会损坏不相关的数据或堆的数据结构(这通常会导致以后崩溃,因为分配器发现其数据不一致)


注释

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

  • 当您的程序有未定义的行为时,不要期望任何可预测的结果。内存中没有内存限制