段错误主要指访问的内存地址超出了系统给这个进程的内存空间。
通常这个值是由gdtr来保存的,他是一个48位的寄存器,其中的32位是保存由它指向的gdt表,后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向的gdt是由以64位为一个单位的表,在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。一旦一个程序发生了越界访问,cpu就会产生相应的异常保护,于是segmentation fault就出现了。
1)错误的访问类型引起
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
上述程序编译没有问题,但是运行时弹出SIGSEGV。此例中,”hello world”作为一个常量字符串,在编译后会被放在.rodata节(GCC),最后链接生成目标程序时.rodata节会被合并到text segment与代码段放在一起,故其所处内存区域是只读的。这就是错误的访问类型引起的SIGSEGV。
(2)访问了不属于进程地址空间的内存
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
还有另一种可能,往受到系统保护的内存地址写数据,最常见就是给一个指针以0地址:
1 2 3 4 5 6 7 |
|
(3)访问了不存在的内存
最常见的情况不外乎解引用空指针了,如:
int *p = null;
*p = 1;
在实际情况中,此例中的空指针可能指向用户态地址空间,但其所指向的页面实际不存在。
(4)内存越界,数组越界,变量类型不一致等
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
这就是明显的数组越界了,或者这个地址根本不存在。
(5)试图把一个整数按照字符串的方式输出
1 2 3 4 5 6 7 8 9 10 11 |
|
这是什么问题呢?由于还不熟悉调试动态链接库,所以我只是找到了printf的源代码的这里。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
仔细看看,发现了这样一个问题,在打印字符串的时候,实际上是打印某个地址开始的所有字符,但是当你想把整数当字符串打印的时候,这个整数被当成了一个地址,然后printf从这个地址开始去打印字符,直到某个位置上的值为\0。所以,如果这个整数代表的地址不存在或者不可访问,自然也是访问了不该访问的内存——segmentation fault。
类似的,还有诸如:sprintf等的格式控制问题,比如,试图把char型或者是int的按照%s输出或存放起来,如:
1 2 3 4 5 6 7 8 9 10 11 |
|
(6)栈溢出了,有时SIGSEGV,有时却啥都没发生