C语言程序调试方法

什么是程序调试?

程序编写完成或在编写过程中,需要对程序进行测试,根据测试发现的错误,进一步诊断,找出发生错误的原因和具体代码位置进行修改,这个过程称为程序调试。在一些情况下,可能需要查看或跟踪程序的运行状态,这种情况也属于程序调试。
在C程序中,程序调试有多种方式可以使用:可以使用C语言的printf等输出函数在怀疑出错的代码位置输出调试信息,例如输出变量的内容等;也可以使用assert语句(断言语句)输出调试信息;还可以将调试信息输出到log文件,通过log文件了解程序的运行状况,定位发生错误的代码位置;还可以在代码中设置断点,跟踪程序的运行状态。

输出调试信息

在程序运行过程中,使用C语言的printf函数、assert断言、输出日志文件等方式输出调试信息,是最容易掌握,也是最方便使用的一种程序调试方法。
在容易出错的代码位置,使用printf函数输出变量的值,根据输出的内容来发现程序出错原因;或使用assert断言来检查条件是否满足,若条件不满足则输出调试信息,并终止程序的运行;或者将所有调试信息输出到一个文件,该文件也称为日志文件,通过查阅日志文件可以很容易判断程序出错的原因,并定位错误的代码位置。

使用printf函数输出调试信息

使用printf函数可以在程序中插入打印语句,在关键位置输出变量的值,以便观察结果。
例1:使用printf函数调试程序,输出变量的值
#include <stdio.h>
int main() {
square(10);
}


/**
函数功能:计算x的二次幂
*/
int square(int x)
{
// 输出调试信息,观察参数x的值
printf("x=%d\n",x);
// 计算x的二次幂
x = x * x;
// 输出调试信息,观察计算后的结果
printf("x*x=%d\n",x);
}

例2:发现程序出错的原因

#include <stdio.h>
int main() {
div(10,3);
}


/**
函数功能:计算两数的商
*/
int div(int a,int b)
{
int c;
// 输出a、b的值,发现发生异常的原因
printf("a=%d;b=%d\n",a,b);
c = a / b;
// 输出计算结果
printf("a/b = %d\n",c);
}

例2代码使用printf函数输出参数a和b的值,当程序发生错误时,可以查看a和b的值,找到程序发生异常的原因。在例2中,参数b为0时就会发生错误。

使用assert断言

assert断言用于条件验证,若验证的条件不满足则输出错误信息,并终止程序的运行。例如在例2中,可以使用assert断言验证b不等于0的条件。
// 断言表达式b!=0
int  c;
assert(b!=0);

若b为0,则控制台输出如下的错误信息,并终止程序的运行。

Assertion failed: b!=0, file d:\home\sample03\sample03\main.c, line 14
请按任意键继续. . .
assert断言是一个宏定义,它不是一个函数,它接受一个表达式作为参数。若该表达式计算结果为真(返回值非零),assert不会输出任何信息,也不会终止程序;若该表达式计算结果为假(返回值为零),assert会在标准错误流 stderr 中写⼊⼀条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。

输出日志文件

前面介绍的调试方法是把调试信息输出到控制台,在控制台可以实时查看程序运行状态,但是不能存储调试信息,方便日后查看。
实际上除了把调试信息输出到控制台,还可以把调试信息输出到文件,存储调试信息的文件也称为程序的日志文件,它是有效帮助我们快速定位,找到程序异常点的实用方法。
下面的代码段将调试信息输出到日志文件:
#include <stdio.h>
#include <stdlib.h>


void write_log(char *message) {
    FILE *log_file = fopen("log.txt", "a"); // 打开日志文件
    if (log_file == NULL) {
        printf("打开日志文件失败");
        return;
    }
    // 写入日志信息
    fprintf(log_file, "%s\n", message); 
// 关闭日志文件   
    fclose(log_file); 
}


int main() {
    char *log_message = "This is a log entry.";
    write_log(log_message); // 写入日志
    return 0;
}

上述代码定义了一个write_log函数,该函数将一条日志信息写入到名称为log.txt的文件,log.txt文件即为日志文件。

设置断点,跟踪程序的运行状态

在程序内设置断点、跟踪程序的运行状态,要使用调试器工具。Visual Studio 集成开发环境已经把调试工具集成到内部,若使用Visua1 Studio开发程序,可以可以直接使用Visual Studio内部集成的调试工具。调试工具的使用方法和技巧,以Visual Studio 2010为例来说明。

断点、单步执行等调试概念

调试过程通常包括设置断点、单步执行代码、查看变量值,方便开发人员逐步跟踪程序的执行流程,排查导出程序出现异常的问题。
在设置断点调试程序之前,我们先理解几个与程序调试相关的概念。
断点(Breakpoints):断点主要设置到代码行,在调试状态下,当程序执行到断点后,程序会暂时停止执行,此时开发人员可以查看当前的变量值、执行流程等信息、
单步执行(Stepping):通过单步执行,开发人员可以逐行跟踪代码的执行过程,观察每一步的结果。Visual Studio 2010 提供了多种单步执行选项,如逐语句、逐过程等。
变量查看(Variable Watching):在调试过程中,开发人员可以实时查看和修改变量的值,以便了解程序的当前状态。
调用堆栈(Call Stack):调用堆栈显示了程序当前执行的函数调用层次结构,这对于理解程序执行流程和定位错误非常有帮助。

调试的基本步骤

(1)打开项目
在 Visual Studio 2010 中打开要调试的项目,设置项目为Debug版本,Debug版本为调试版本,它包含调适信息,对程序不做任何优化,便于程序员调试程序。
(2)设置断点
打开需要调试的C源代码文件,将光标放置到需要设置断点的代码行,按下F9快捷键,或选择“调试”菜单的“切换断点”命令,即可将断点设置到该代码行。
上图在代码行左侧显示的红色小球,即为该代码行的断点标志。
(3)启动调试
选择“调试”菜单中的“开始调试”或按 F5 键启动调试。
调试启动后,程序执行到断点位置会暂时停止,等待开发人员执行调试指令。展开“调试”菜单,会看到相关的调试指令。
停止调试(E)指令,快捷键为Shift+F5,该指令停止调试过程。
逐语句(I)指令,快捷键为F11,该指令从当前断点开始,逐条执行断点及断点后面的语句,在函数调⽤的地⽅, 想进⼊函数观察细节,必须使⽤F11,如果使⽤F10 ,直接完成函数调⽤。
逐过程(O)指令,快捷键为F10,该指令执行一个过程,过程可以是一次函数调用,或者是一条语句。
跳出(T)指令,快捷键为Shift+F11,该指令执行当前执行点所在函数中剩余未执行的行。下个被显示的语句是紧随在该过程调用后的语句。所有在当前与最后的执行点间的代码都会被执行。
(5)调试窗口
程序启动调试后,会在设置的断点处停止执行。调试窗口如下图所。
代码窗口:显示执行的代码,代码行左侧的黄色箭头指向当前正在执行的代码行,
在此窗口内开发人员可以执行相关的调试指令。
变量查看窗口:开发人员可以通过该窗口,查看当前正在执行语句上下文相关的变量值。
堆栈查看窗口:开发人员可以通过该窗口,查看当前堆栈上的函数或过程调用。