为什么在发生段故障时索引不同

时间:2014-05-13 08:18:32

标签: c

首先,我编写了下面的代码(我知道代码很糟糕,但只是为了测试):

#include <stdio.h>

int ints[20] = {10, 20};
int main()
{
    printf("%d", ints[-44]); //ints[-552] will not encounter segmentfault in my machine
}

我机器中的整数的内存地址是0x600840,而在我的机器中,内存页面的大小是4096字节,因此,0x600000和0x600fff之间的内存地址是可读的,所以我直到整数[-553](552 * 4 == 0x840)才会遇到segmentfault

但是,当我将代码更改为低于1时:

#include <stdio.h>

int ints[20] = {10, 20};
int main()
{
    ints[-44] = 0; 
}

此时,使用语句ints[-512] = 0时会发生段故障,但在第一版代码中,printf("%d", ints[-512])将不会遇到段故障。

我的问题是什么导致了这个?如何在ints[threshold + 1] = 0使用ints[threshold]时确定导致段故障的阈值,而{{1}}不会导致段故障?

2 个答案:

答案 0 :(得分:2)

在您分配的内存范围之外进行读写是undefined behaviour。通常(但不总是)调用此未定义的行为会导致段错误。 但是,你不能确定会发生段错误。

维基百科链接很好地解释了它:

  

当出现未定义行为的实例时,就语言规范而言,任何事情都可能发生,也许什么都没发生。

因此,在这种情况下,您可能会遇到段错误,程序可能会中止,或者有时可能会运行正常。或者,任何事情。没有办法保证结果。

你问:

  

我如何确定导致段故障的阈值?

由于ints是一个包含20个项目的数组,因此可以确定不会发生段错误的唯一索引是0-19。您无法编写您确定会跨系统触发分段错误的代码。这个link解释得非常好:

  

- Macro:int SIGSEGV

     

当程序试图在为其分配的内存之外读取或写入时,或者写入只能读取的内存时,会生成此信号。 (实际上,信号仅在程序远远超出系统内存保护机制检测到的情况下才会发生。)该名称是“分段违规”的缩写。

     

获取SIGSEGV条件的常用方法包括取消引用null或未初始化的指针,或者当您使用指针逐步执行数组时,但无法检查数组的结尾。无论是取消引用空指针还是生成SIGSEGV或SIGBUS,它都会因系统而异。

(强调补充)

答案 1 :(得分:0)

阅读是另一种操作,而不是写作。可写数据段之前的页面可能是只读的。我们来看看。

我用这种方式修改了你的第一个程序:

#include <stdio.h>

int ints[20] = {10, 20};
int main()
{
    printf("%d", ints[-44]); //ints[-552] will not encounter segmentfault in my machine
    close((int)&ints);
    sleep(100);
}

这样在strace输出中地址就会变得明显。我跑了它并获得134520896 0x804a040。看一下/proc/<pid>/maps会发现

08048000-08049000 r-xp 00000000 00:21 5275899    /tmp/a
08049000-0804a000 r--p 00000000 00:21 5275899    /tmp/a
0804a000-0804b000 rw-p 00001000 00:21 5275899    /tmp/a
...

我。即地址是该页面开头后面的64个字节。之前的内存是只读的。

如果我对第二个程序做同样的事情:

int ints[20] = {10, 20};
int main()
{
    close((int)&ints);
    ints[-44] = 0;
    sleep(100);
}

我也得到134520896 = 0x804a040

地图与上图相同:

08048000-08049000 r-xp 00000000 00:21 5277100    /tmp/b
08049000-0804a000 r--p 00000000 00:21 5277100    /tmp/b
0804a000-0804b000 rw-p 00001000 00:21 5277100    /tmp/b
...

以整数计算的-44偏移量会使地址减少44 * sizeof(int),i。即176 = 0xb0,它引导我0x804a040-0xb0 = 0x8049f80,它位于只读区域08049000-0804a000。读这不会导致任何sefgault,但写作确实如此。

在给定的情况下,我不应该在ints[-2064]之前得到段错误,因为它的地址为0x8048000。但是,由于您可能拥有与我不同的编译器,库,设置等,您可能会遇到其他限制。如你所知,对于未定义的行为,你永远不会安全。

但是,让我们测试一下:

为了避免使用可能会更改细分受众群规模的printf(),我会

int ints[20] = {10, 20};
int main()
{
    int i = 0;
    while (1) {
        i--;
        close(i);
        close((int)&ints[i]);
        ints[i] = ints[i];
    }
}

并查看strace输出,其中包含

close(-15)                              = -1 EBADF (Bad file descriptor)
close(134520836)                        = -1 EBADF (Bad file descriptor)
close(-16)                              = -1 EBADF (Bad file descriptor)
close(134520832)                        = -1 EBADF (Bad file descriptor)
close(-17)                              = -1 EBADF (Bad file descriptor)
close(134520828)                        = -1 EBADF (Bad file descriptor)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x8049ffc} ---
+++ killed by SIGSEGV +++

其中134520832 = 0x804a000134520828 = 0x8049ffc。我可能不写 - &gt; SIGSEGV。

如果我刚看完:

int ints[20] = {10, 20};
int main()
{
    int i = 0; int j;
    while (1) {
        i--;
        close(i);
        close((int)&ints[i]);
        j = ints[i];
    }
}

我确实要等到

close(-2064)                            = -1 EBADF (Bad file descriptor)
close(134512640)                        = -1 EBADF (Bad file descriptor)
close(-2065)                            = -1 EBADF (Bad file descriptor)
close(134512636)                        = -1 EBADF (Bad file descriptor)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x8047ffc} ---
+++ killed by SIGSEGV +++
Speicherzugriffsfehler

其中134512640 = 0x8048000134512636 = 0x8047ffc。这不在我的映射内存中,我甚至不允许阅读。