什么是段错误(Segmentation fault)
段错误主要在unix-like的操作系统中出现(在windows中,它叫做 access violation),
当我们的进程访问了他不该访问的内存地址时(后面会介绍),操作系统就会发送一个SIGSEGV信号给到进程。该信号可以被捕获,但是通常情况下,我们会使用操作系统默认的信号处理器,这个默认的SIGSEGV信号处理器的策略就是终止进程。如下图所示:
额,这就是段错误。那什么情况下会导致段错误呢?
SIGSEGV产生的条件
如前所述,进程访问了它不该访问的内存地址就会出现段错误。
如上图所示,不允许进程访问的有:
- 内核空间
- 待分配区域
- 保留区
除此之外,进程修改只读内存,也会导致段错误发生。接下来我们来看看常见的段错误场景吧:
- 解引用空指针
int *p = 0; std::cout << *p << std::endl; // Segmentation fault
访问了保留区
- 访问非未初始化的指针
这段代码不一定会导致段错误,最终还是要看p具体指向何处。int *p; std::cout << *p << std::endl;
- 内存越界
额,事实上,这段代码并没有出现段错误。为啥,我想是内存申请都是以页(通常是4096)为单位,而且new/malloc本身也需要一些内存来记录内存信息。因此我们换一个例子:char *s = new char[10]; for (int i = 0; i < 12; ++i) { s[i] = 'c'; }
我们通过char *ptr = (char*)sbrk(10); strcpy(ptr, "123456789"); ptr[9] = '\0'; std::cout << ptr << std::endl; *(ptr + 4095) = 'x'; // ok std::cout << "4095 ... ok" << std::endl; *(ptr + 4096)= 'x'; // segmentation fault
sbrk
系统调用来直接分配内存,在测试环境,段错误是必现的。内存越界会导致段错误的原因还是访问了未分配的内存。 - 修改只读内存。
const char *str = "hello, world!!!"; const_cast<char*>(str)[0] = 'H'; // segmentation fault
- 堆栈溢出
int main() { main(); return 0; }
- ...
大家发现没,段错误的出现几乎都和访问/修改内存有关。
我们再来看一个例子:
class A {
public:
int func_a() {}
virtual int func_b() {}
};
int main() {
A *a = nullptr;
a->func_a();
std::cout << "你猜我能不能打印出来~~" << std::endl;
a->func_b();
return 0;
}
嗯,上面的例子打印是会正常出来的。为啥??
func_a作为A的成员函数,其调用等价于:
A *a = nullptr;
A::func_a(a);
这里,并不涉及到内存访问/修改,因此并不会出现段错误。
相反,func_b的调用,由于涉及到查虚函数表的操作,会出现段错误。
最后
段错误的出现条件还是比较简单的,但是实际开发中出现的段错误往往都令人头疼(之前就遇到过由悬空指针导致的段错误,还是偶现的)。为了减少段错误的发生,我们需要仔细检查代码中指针的使用、数组边界控制等,并尽可能采用现代C++提供的安全特性(如智能指针、基于范围的for循环等)。
标签:场景,错误,int,常见,char,访问,内存,func From: https://www.cnblogs.com/liutimo/p/18308410