当我写一个数组的末尾时,为什么我的程序没有崩溃?

vpr*_*jan 16 c stack callstack

为什么下面的代码没有任何崩溃@ runtime?

而且尺寸完全取决于机器/平台/编译器!! 我甚至可以在64位机器上放弃200.如何在OS中检测到主函数中的分段错误?

int main(int argc, char* argv[])
{
    int arr[3];
    arr[4] = 99;
}
Run Code Online (Sandbox Code Playgroud)

这个缓冲空间来自哪里?这是分配给进程的堆栈吗?

Fre*_*ihl 75

我之前写的一些用于教育目的的东西......

考虑以下c程序:

int q[200];

main(void) {
    int i;
    for(i=0;i<2000;i++) {
        q[i]=i;
    }
}
Run Code Online (Sandbox Code Playgroud)

在编译并执行它之后,会生成核心转储:

$ gcc -ggdb3 segfault.c
$ ulimit -c unlimited
$ ./a.out
Segmentation fault (core dumped)
Run Code Online (Sandbox Code Playgroud)

现在使用gdb执行事后分析:

$ gdb -q ./a.out core
Program terminated with signal 11, Segmentation fault.
[New process 7221]
#0  0x080483b4 in main () at s.c:8
8       q[i]=i;
(gdb) p i
$1 = 1008
(gdb)
Run Code Online (Sandbox Code Playgroud)

呵呵,当一个人在分配的200个项目之外写的时候,程序没有发生段错误,而是当i = 1008时崩溃了,为什么呢?

输入页面.

可以在UNIX/Linux上以多种方式确定页面大小,一种方法是使用系统函数sysconf(),如下所示:

#include <stdio.h>
#include <unistd.h> // sysconf(3)

int main(void) {
    printf("The page size for this system is %ld bytes.\n",
            sysconf(_SC_PAGESIZE));

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

它给出了输出:

该系统的页面大小为4096字节.

或者可以使用命令行实用程序getconf,如下所示:

$ getconf PAGESIZE
4096
Run Code Online (Sandbox Code Playgroud)

验尸

事实证明,segfault不是在i = 200时发生,而在i = 1008时,让我们找出原因.启动gdb进行一些事后分析:

$gdb -q ./a.out core

Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
[New process 4605]
#0  0x080483b4 in main () at seg.c:6
6           q[i]=i;
(gdb) p i
$1 = 1008
(gdb) p &q
$2 = (int (*)[200]) 0x804a040
(gdb) p &q[199]
$3 = (int *) 0x804a35c
Run Code Online (Sandbox Code Playgroud)

q结束于地址0x804a35c,或者更确切地说,q [199]的最后一个字节位于该位置.页面大小与我们之前看到的4096字节相同,机器的32位字大小使虚拟地址分解为20位页码和12位偏移量.

q []以虚拟页码结尾:

0x804a = 32842偏移量:

0x35c = 860所以仍有:

4096 - 864 =在分配q []的内存页面上剩余3232个字节.这个空间可以容纳:

3232/4 = 808个整数,代码将其视为包含位置200到1008的q元素.

我们都知道这些元素不存在,编译器没有抱怨,因为我们对该页面有写权限,所以hw也没有.只有当i = 1008时,q []指的是我们没有写入权限的不同页面上的地址,虚拟内存hw检测到这一点并触发了段错误.

整数存储在4个字节中,这意味着该页面包含808(3236/4)个额外的伪元素,这意味着从q [200],q [201]一直到元素199访问这些元素仍然是完全合法的. + 808 = 1007(q [1007]),不触发seg故障.访问q [1008]时,您输入权限不同的新页面.

  • 优秀的答案,除了你说"从q [200],q [201]一直到元素访问这些元素仍然是完全合法的部分" - 它发生*对于这个编译器实现*,访问这些元素不会导致任何问题,但技术上访问这些元素是未定义的行为,并且不同的编译器可以自由地生成非常不同的结果.即,访问这些元素是非法的,但在这种情况下,你可以逃脱它.就像速度限制是65英里每小时的速度是75英里每小时.:) (13认同)
  • +1虽然我同意爱德华."合法性"是非常严格的定义; 我们不要在这里弯曲它的含义! (5认同)
  • 这绝对令人着迷,这是我读过的最好的帖子之一. (2认同)

NPE*_*NPE 6

由于您是在数组边界之外编写的,因此代码的行为未定义.

任何事情都可能发生,这是未定义行为的本质,包括缺少段错误(编译器没有义务执行边界检查).

你写的是你没有分配的内存,但恰好在那里,而且 - 可能 - 没有被用于其他任何东西.如果对代码中看似无关的部分,操作系统,编译器,优化标记等进行更改,则代码的行为可能会有所不同.

换句话说,一旦你进入那个领域,所有的赌注都会被取消.