首先,我编写了下面的代码(我知道代码很糟糕,但只是为了测试):
#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}}不会导致段故障?
答案 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 = 0x804a000
和134520828 = 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 = 0x8048000
和134512636 = 0x8047ffc
。这不在我的映射内存中,我甚至不允许阅读。