对C语言进行调试的最好方法是什么?

要了解调试程序的最好方法,首先要分析一下调试过程的三个要素:
应该用什么工具调试一个程序?
用什么办法才能找出程序中的错误?
怎样才能从一开始就避免错误?
应该用什么工具调试一个程序?

有经验的程序员会使用许多工具来帮助调试程序,包括一组调试程序和一些"lint”程序,当然,编译程序本身也是一种调试工具。

在检查程序中的逻辑错误时,调试程序是特别有用的,因此许多程序员都把调试程序作为基本的调试工具。一般来说,调试程序能帮助程序员完成以下工作:
(1)观察程序的运行情况
仅这项功能就使一个典型的调试程序具备了不可估量的价值。即使你花了几个月的时间精心编写了一个程序,你也不一定完全清楚这个程序每一步的运行情况。如果程序员忘记了某些if语句、函数调用或分支程序,可能会导致某些程序段被跳过或执行,而这种结果并不是程序员所期望的。不管怎样,在程序的执行过程中,尤其是当程序有异常表现时,如果程序员能随时查看当前被执行的是那几行代码,那么他就能很好地了解程序正在做什么以及错误发生在什么地方。

(2)设置断点
通过设置断点可以使程序在执行到某一点时暂时停住。当你知道错误发生在程序的哪一部分时,这种方法是特别有用的。你可以把断点设置在有问题的程序段的前面、中间或后面。当程序执行到断点时,就会暂时停住,此时你可以检查所有局部变量、参数和全局变量的值。如果一切正常,可以继续执行程序,直到遇到另一个断点,或者直到引起问题的原因暴露出来。

(3)设置监视
程序员可以通过调试程序监视一个变量,即连续地监视一个变量的值或内容。如果你清楚一个变量的取值范围或有效内容,那么通过这种方法就能很快地找出错误的原因。此外,你可以让调试程序替你监视变量,并且在某个变量超出预先定义的取值范围或某个条件满足时使程序暂停执行。如果你知道变量的所有行为,那么这么做是很方便的。

好的调试程序通常还提供一些其它功能来简化调试工作。然而,调试程序并不是唯一的调试工具,lint程序和编译程序本身也能提供很有价值的手段来分析程序的运行情况。

注意:lint程序能分辨数百种常见的编程错误,并且能报告这些错误发生在程序的哪一部分。尽管其中有一些并不是真正的错误,但大部分还是有价值的。

lint程序和编译程序所提供的一种典型功能是编译时检查(compile—time checks),这种功能是调试程序所不具备的。当用这些工具编译你的程序时,它们会找出程序中有问题的程序段,可能产生意想不到的效果的程序段,以及常见的错误。下面将分析几个这种检查方式的应用例子,相信对你会有所帮助。

等于运算符的误用

编译时检查有助于发现等于运算符的误用。请看下述程序段:
    void foo(int a,int b)
    {
      if ( a = b )
      {
          / * some code here * /
      }
    }
这种类型的错误一般很难发现!程序并没有比较两个变量,而是把b的值赋给了a,并且在b不为零的条件下执行if体。一般来说,这并不是程序员所希望的(尽管有可能)。这样一来,不仅有关的程序段将被执行错误的次数,并且在以后用到变量a时其值也是错误的。

未初始化的变量

编译时检查有助于发现未初始化的变量。请看下面的函数:
void average ( float ar[], int size )
{
       float total;
       int a;
       for( a = 0;a<size; ++a)
       {
            total+=ar[a];
       }
       printf(" %f\n", total /  (float) size );
}
这里的问题是变量total没有被初始化,因此它很可能是一个随机的无用的数。数组所有元素的值的和将与这个随机数的值相加(这部分程序是正确的),然后输出包括这个随机数在内的一组数的平均值。

变量的隐式类型转换

在有些情况下,C语言会自动将一种类型的变量转换为另一种类型。这可能是一件好事(程序员不用再做这项工作),但是也可能会产生意想不到的效果。把指针类型隐式转换成整型恐怕是最糟糕的隐式类型转换。
void sort( int ar[],int size )
{
       /* code to sort goes here * /
}
int main()
{
       int arrgy[10];
       sort(  10, array );
}
上述程序显然不是程序员所期望的,虽然它的实际运行结果难以预测,但无疑是灾难性的。

用什么办法才能找出程序中的错误?

在调试程序的过程中,程序员应该记住以下几种技巧:
先调试程序中较小的组成部分,然后调试较大的组成部分
如果你的程序编写得很好,那么它将包含一些较小的组成部分,最好先证实程序的这些部分是正确的。尽管程序中的错误并不一定发生在这些部分中,但是先调试它们有助于你理解程序的总体结构,并且证实程序的哪些部分不存在错误。进一步地,当你调试程序中较大的组成部分时,你就可以确信那些较小的组成部分是正常工作的。

彻底调试好程序的一个组成部分后,再调试下一个组成部分
这一点非常重要。如果证实了程序的一个组成部分是正确的,不仅能缩小可能存在错误的范围,而且程序的其它组成部分就能安全地使用这部分程序了。这里应用了一种很好的经验性原则,简单地说就是调试一段代码的难度与这段代码长度的平方成正比,因此,调试一段20行的代码比调试一段10行的代码要难4倍。因此,在调试过程中每次只把精力集中在一小段代码上是很有帮助的。当然,这仅仅是一个总的原则,具体使用时还要视具体情况而定。

连续地观察程序流(flow)和数据的变化
这一点也很重要!如果你小心仔细地设计和编写程序,那么通过监视程序的输出你就能准确地知道正在执行的是哪部分代码以及各个变量的内容都是什么。当然,如果程序表现不正常,你就无法做到这一点。为了做到这一点,通常只能借助于调试程序或者在程序中加入大量的print语句来观察控制流和重要变量的内容。

始终打开编译程序警告选项  并试图消除所有警告
在开发程序的过程中,你自始至终都要做到这一点,否则,你就会面临一项十分繁重的工作。尽管许多程序员认为消除编译程序警告是一项繁琐的工作,但它是很有价值的。编译程序给出警告的大部分代码至少都是有问题的,因此用一些时间把它们变成正确的代码是值得的;而且,通过消除这些警告,你往往会找到程序中真正发生错误的地方。

准确地缩小存在错误的范围
如果你能一下子确定存在错误的那部分程序并在其中找到错误,那就会节省许多调试时间,并且你能成为一个收入相当高的专业调试员。但事实上,我们并不能总是一下子就命中要害,因此,通常的做法是逐步缩小可能存在错误的程序范围,并通过这种过程找出真正存在错误的那部分程序。不管错误是多么难于发现,这种做法总是有效的。当你找到这部分程序后,就可以把所有的调试工作集中到这部分程序上了。不言而喻,准确地缩小范围是很重要的,否则,最终集中精力调试的那部分程序很可能是完全正确的。

如何从一开始就避免错误?

有这样一句谚语——“防患于未然”,它的意思是避免问题的出现比出现问题后再想办法弥补要好得多。这在计算机编程中也是千真万确的!在编写程序时,一个经验丰富的程序员所花的时间和精力要比一个缺乏经验的程序员多得,但正是这种耐心和严谨的编程风格使经验丰富的程序员往往只需花很少的时间来调试程序,而且,如果此后程序要解决某个问题或做某种改动,他便能很快地修正错误并加入相应的代码。相反,对于一个粗制滥造的程序,即使它总的来说还算正确,那么改动它或者修正其中一个很快就暴露出来的错误,都会是一场恶梦。

一般来说,按结构化程序设计原则编写的程序是易于调试和修改的,下面将介绍其中的一些原则。

程序中应有足够的注释
有些程序员认为注释程序是一项繁琐的工作,但即使你从来没想过让别人来读你的程序,你也应该在程序中加入足够的注释,因为即使你现在认为清楚明了的语句,在几个月以后往往也会变得晦涩难懂。这并不是说注释越多越好,过多的注释有时反而会混淆代码的原意。但是,在每个函数中以及在执行重要功能或并非一目了然的代码前加上几行注释是必要的。下面就是一段注释得较好的代码:
  /*  
   *   Compute an integer factorial value using recursion.
   *   Input   an integer number.
   *   Output  : another integer
   *   Side effects : may blow up stack if input value is  * Huge *
   */
int factorial ( int number)
{
       if ( number < = 1)
           return 1;  /* The factorial of one is one; QED * /
       else
            return n * factorial( n - 1 );
       / * The magic! This is possible because the factorial of a
         number is the number itself times the factorial  of the
         number minus one.  Neat!  * /
}
函数应当简洁
按照前文中曾提到的这样一条原则——调试一段代码的难度和这段代码长度的平方成正比——函数编写得简洁无疑是有益的。但是,需要补充的是,如果一个函数很简洁,你就应该多花一点时间去仔细地分析和检查它,以确保它准确无误。此后你可以继续编写程序的其余部分,并且可以对刚才编写的函数的正确性充满信心,你再也不需要检查它了。对于一段又长又复杂的例程,你往往是不会有这样的信心的。

编写短小简洁的函数的另一个好处是,在编写了一个短小的函数之后,在程序的其它部分就可以使用这个函数了。例如,如果你在编写一个财务处理程序,那么你在程序的不同部分可能都需要按季、按月、按周或者按一月中的某一天等方式来计算利息。如果按非结构化原则编写程序,那么在计算利息的每一处都需要一段独立的代码,这些重复的代码将使程序变得冗长而难读。然而,你可以把这些任务的实现简化为下面这样的一个函数:
  /*
   *    ComDllte what the "real" rate of interest would be
   *     for a given flat interest rate, divided into N segments
   */
double Compute Interest( double Rate, int Segments )
{
       int  a;
       double Result = 1.0;
       Rate /= (double) Segments;
       for( a = 0; a< Segments  ; ++a )
           Result * =Rate;
       return Result;
}
在编写了上述函数之后,你就可以在计算利息的每一处调用这个函数了。这样一来,你不仅能有效地消除每一段复制的代码中的错误,而且大大缩短了程序的长度,简化了程序的结构。这种技术往往还会使程序中的其它错误更容易被发现。

当你习惯了用这种方法把程序分解为可控制的模块后,你就会发现它还有更多的妙用。

程序流应该清晰,避免使用goto语句和其它跳转语句
这条原则在计算机技术领域内已被广泛接受,但在某些圈子中对此还很有争议。然而,人们也一致认为那些通过少数语句使程序流无条件地跳过部分代码的程序调试起来要容易得多,因为这样的程序通常更加清晰易懂。许多程序员不知道如何用结构化的程序结构来代替那些“非结构化的跳转”,下面的一些例子说明了应该如何完成这项工作:
for( a = 0; a<100s ++a)
{
     Func1( a );
     if (a  = = 2 ) continue;
     Func2( a );
}

当a等于2时,这段程序就通过continue语句跳过循环中的某余部分。它可以被改写成如下的形式:
for( a = 0; a<100; ++a)
{
     Func1 (a);
     if (a !=2 )
        Func2(a) ;
}
这段程序更易于调试,因为花括号内的代码清楚地显示了应该执行和不应该执行什么。那么,它是怎样使你的代码更易于修改和调试的呢?假设现在要加入一些在每次循环的最后都要被执行的代码,在第一个例子中,如果你注意到了continue语句,你就不得不对这段程序做复杂的修改(不妨试一下,因为这并非是显而易见的!);如果你没有注意到continue语句,那么你恐怕就要犯一个难以发现的错误了。在第二个例子中,要做的修改很简单,你只需把新的代码加到循环体的末尾。

当你使用break语句时,可能会发生另外一种错误。假设你编写了下面这样一段程序:
for (a =0) a<100;  ++a)
{
     if (Func1 (a) ==2 )
        break;
     Func2 (a)  ;
}
假设函数Funcl()的返回值永远不会等于2,上述循环就会从1进行到100;反之,循环在到达100以前就会结束。如果你要在循环体中加入代码,看到这样的循环体,你很可能就会认为它确实能从0循环到99,而这种假设很可能会使你犯一个危险的错误。另一种危险可能来自对a值的使用,因为当循环结束后,a的值并不一定就是100。

c语言能帮助你解决这样的问题,你可以按如下形式编写这个for循环:
    for(a=O;a<100&&Func1(a)!=2;++a)
上述循环清楚地告诉程序员:“从0循环到99,但一旦Func1()等于2就停止循环”。因为整个退出条件非常清楚,所以程序员此后就很难犯前面提到的那些错误了。

函数名和变量名应具有描述性
使用具有描述性的函数和变量名能更清楚地表达代码的意思——并且在某种程度上这本身就是一种注释。以下几个例子就是最好的说明:
    y=p+i-c;

    YearlySum=Principal+Interest-Charges:
哪一个更清楚呢?
    p=*(l+o);

    page=&List[offset];
哪一个更清楚呢?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/450580.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何赋值hook定义的变量

1、定义变量 const { tableProps, mutate} useRequest(async (params {}) > {const { success, data, total } await Api.getUserAccountApi({page: params.current || 1,...searchValue,});return {list: success ? data : [],total: success ? total : 0,};},{pagin…

java中的sleep()和wait()的区别

对于sleep()方法&#xff0c;我们首先要知道该方法是属于Thread类中的。而wait()方法&#xff0c;则是属于Object类中的。sleep()方法导致了程序暂停执行指定的时间&#xff0c;让出cpu该其他线程&#xff0c;但是他的监控状态依然保持者&#xff0c;当指定的时间到了又会自动恢…

Webpack4干货分享(二),使用loader处理scss,图片以及转换JS

转载请注明出处&#xff1a; 葡萄城官网 &#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 原文出处&#xff1a; https://wanago.io/2018/07/16/webpack-4-course-part-two-webpack-4-course-part-two-loaders/今天继续我们的Webpack 4…

spring-data-jpa 介绍 复杂查询,包括多表关联,分页,排序

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 本篇进行Spring-data-jpa的介绍&#xff0c;几乎涵盖该框架的所有方面&#xff0c;在日常的开发当中&#xff0c;基本上能满足所有需求。…

重写laravel的request的校验器

protected function failedValidation(Validator $validator) {$error $validator->errors()->all();throw new HttpResponseException(response()->json([data>[],code>500,msg>$error[0]], 500)); }

如何检测C语言中的内存漏洞(leak)?

在动态分配的内存单元(即由函数malloc()或ealloc()分配的内存单元)不再使用却没有被释放的情况下&#xff0c;会出现内存漏洞。未释放内存单元本身并不是一种错误&#xff0c;编译程序不会因此报告出错&#xff0c;程序也不会因此而立即崩溃。但是&#xff0c;如果不再使用而又…

Oracle中row_number()、rank()、dense_rank() 的区别

link:https://www.cnblogs.com/qiuting/p/7880500.html转载于:https://www.cnblogs.com/Spring-Rain/p/9716213.html

Vim使用教程(按键教程,映射都可以改的,持续更新)

修改映射 tnvim .vimrc 1、spaceft 调出目录 2、controlh 跳到目录 3、controll 跳到内容页 4、spacekn 运行最近一个测试 5、spacekf 运行整个测试文件 6、spacekl 运行最后一个测试 7、sv 分屏 8、sq 退出分屏 9、controlp 搜索功能 10、/搜索内容 N向上…

Alpha 冲刺五

团队成员 051601135 岳冠宇051604103 陈思孝031602629 刘意晗031602248 郑智文031602234 王淇会议照片 项目燃尽图 项目进展 暂无实质性进展。 项目描述 问题困扰&#xff1a; 商品分类出现困惑。交互部分向服务器发送请求失败&#xff0c;安卓在4.0后对网络请求有限制要求&…

什么是换码符(escape character)?

换码符是用来执行一个命令或一项任务的字符&#xff0c;它们不会被打印到屏幕上。例如&#xff0c;一个换码符可以是这样一个字符&#xff0c;它被传递给一个设备&#xff0c;告诉计算机屏幕以红色而不是通常的白色来显示下一行。这个换码符将和真正要被设备以红色来显示的字符…

java日期工具类DateUtil

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 DateUtil类 [java] view plain copy package com.util; import java.text.SimpleDateFormat; import java.util.ArrayList; import…

Command 传参的几种方式

Command可以根据CommandParameter传参 关键代码 public ICommand SubmitCommand > _submitCommand; private RelayCommand _submitCommand new RelayCommand(new Action<object>(ShowMessage)); private static void ShowMessage(object obj) {MessageBox.Show(obj.T…

phpStorm重构快捷键(mac系统、持续更新)

参考 https://learnku.com/laravel/t/5420/your-keyboard-shortcuts-please 1、ctrlaltf 将表达式提取出来使其成为类的属性&#xff0c;并自动更新引用。 2、ctrlaltp 将表达式变成由参数传入 3、crtlF6 修改函数签名&#xff08;函数名&#xff0c;函数参数&#xff09…

python -m xxx.py和python xxx.py的区别

先看下python -m site作用是显示sys.path的值内容&#xff0c;也就是python搜索模块的目录&#xff0c;作用类似于linux下的PATH python -m SimpleHTTPServer 会在sys.path的所有路径下查找SimpleHTTPServer.py文件&#xff1b; 而python SimpleHTTPServer.py则是在当前查找文…

C语言多维数组与多级指针

多维数组与多级指针也是初学者感觉迷糊的一个地方。超过二维的数组和超过二级的指针其实并不多用。如果能弄明白二维数组与二级指针&#xff0c;那二维以上的也不是什么问题了。所以本节重点讨论二维数组与二级指针。一、二维数组 1、假想中的二维数组布局我们前面讨论过&…

spring-data-jpa 使用

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 只是记录下使用方法 &#xff1a; 1. RPC接口 service实现类调用&#xff0c;PageRequest对象会实现分页、排序。 Overridepublic Obj…

docker的安装与安装mysql(mac,centos为例)

一、mac安装docker 1、去这个网站下载安装https://www.docker.com/get-started 2、运行docker 运行开启docker docker run -dp 80:80 docker/getting-started 二、centos8安装docker 1、安装docker 下载低版本的 yum install -y docker-ce --nobest 2、安装扩展 wget http…

配置PPPOE

先配置服务端PPPOE-Server先为路由添加一个账号为PPP所使用[PPPOE-Server]aaa [PPPOE-Server-aaa]local-user test password cipher 123 //添加一个本地账号[PPPOE-Server-aaa]local-user test service-type ppp//设置test账号类型为PPP账号[PPPOE-Server]ip pool test//添加一…

eclipse弃坑记第一篇之在idea上配置Tomcat环境并创建Javaweb项目的详细步骤原创

IntelliJ IDEA是一款功能强大的开发工具&#xff0c;在代码自动提示、重构、J2EE支持、各类版本工具(如git、svn、github)、maven等方面都有很好的应用。 IntelliJ IDEA有免费的社区版和付费的旗舰版。免费版只支持Java等为数不多的语言和基本的IDE特性&#xff0c;旗舰版还支持…

laravel安装prettier,git hook代码格式化工具

1、安装prettier的php扩展 npm install --global prettier prettier/plugin-php 2、安装husky&#xff0c;lint-staged&#xff08;git钩子&#xff09;使用 npm i prettier lint-staged husky -D 3、修改package.json文件 在scripts后面添加两个函数 "husky": …