差不多每次编一些竞赛类的程序都会至少给我报一次runtime_error(运行时错误)。这或许也是广大OIer心中永远的痛。~_~
本文主要讨论如何对runtime_error以及其中的segment_fault部分进行调试。本文以dev-c++5.11作为环境,由于以竞赛为主,本文不涉及指针、c++中的类等部分。
下面所有经验,均为本人在编程过程中所实际涉及并进行应用。
一、关于runtime_error(运行时错误)
运行时错误大概分为以下几类:
1、访问到不应访问的地址(如数组越界)
2、内存溢出(如数组过大)
3、算术上溢或下溢(如除以0)
4、段错误(各种其它错误)
除最后一种情况的一部分外,剩余错误发生时通常会在执行.exe文件时显示“已停止运行”。
(一)地址越界及溢出
通常情况下(不考虑指针地址非法)数组开过大会导致编译错误(Complie Error)。但是访问下标负数或数组过小会导致访问到数组以外的范围。
除将数组范围增大之外,通常情况下可以开一个新数组,当数组内存溢出时多余内容会流入新数组以避免错误发生(不推荐这种做法,无法确定内容剩余部分)。
(二)算术上溢及下溢
我们可以在每个可能出现错误的位置尝试c++中的try-throw-catch子句。
比如遇到除以0:
#include <iostream> using std::cout; using std::cin; using std::cerr; int fun(int &a, int &b) { if(b == 0) { throw "hello there have zero sorry\n"; //引发异常 } return a / b; } int main() { int a; int b; while(true) { cin >> a; cin >> b; try //try里面是可能引发异常代码块 { cout << " a / b = "<< fun(a,b) << "\n"; } catch(const char *str) //接收异常,处理异常 括号内是类型 { cout << str; cerr <<"除数为0\n"; //cerr不会到输出缓冲中 这样在紧急情况下也可以使用 }
catch(...)//可以捕捉任何类型的异常
{
cerr <<"未知错误\n";
} } system("pause"); return 1;//异常结束 }
try:try块标识符其中特定的异常可能被激活的代码块,他后面跟一个或者多个catch块.
catch:类似于函数定义,但并不是函数定义,关键字catch表明这是给一个处理程序, 里面的const cahr *str 会接受throw传过来错误信息.
throw:抛出异常信息,类似于执行返回语句,因为它将终止函数的执行,但是它不是将控制权交给调用程序,而是导致程序沿着函数调用序列后退,知道找到包含try块的函数.
(三)对segment_fault的处理
下面是来自Answers.com的定义:
A segmentation fault (often shortened to segfault) is a particular error condition that can occur during the operation of computer software. In short, a segmentation fault occurs when a program attempts to access a memory location that it is not allowed to access, or attempts to access a memory location in a way that is not allowed (e.g., attempts to write to a read-only location, or to overwrite part of the operating system). Systems based on processors like the Motorola 68000 tend to refer to these events as Address or Bus errors. |
另外,这里有个基本上对照的中文解释,来自http://www.linux999.org/html_sql/3/132559.htm
所谓的段错误 就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gdtr来保存的,他是一个48位的寄存器,其中的32位是保存由它指向的gdt表, 后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向的gdt是由以64位为一个单位的表,在这张表中 就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。一旦一个程序发生了越界访 问,cpu就会产生相应的异常保护,于是segmentation fault就出现了 |
实际上同前面一样,段错误也是访问到了非法的内存,它会抛出SIGSEGV信号,可以通过signal或者dev-c++自带调试器捕捉。
比如说:
其中scanf调用了a而不是&a
这时我们可以借助c++中的assert函数捕捉异常
首先我们初步判断这里出现了段错误,借助assert的异常判断我们可以看出是否出现错误
在判断确实出现错误以后我们可以借助调试器观察错误位置
经过观察,我们确定错误位于蓝色行的"%d",a处
改正就可以了
段错误是算法竞赛中很容易出现的一种错误,我们应该学会使用各种方法尽快加以解决。