《C专家编程》第二章——这不是Bug,而是语言特性

  无论一门语言有多么流行或多么优秀,它总是存在一些问题,C语言也不例外。本章讨论的重点是C语言本身存在的问题,作者煞费苦心的用一个太空任务和软件的故事开头,也用另一个太空任务和软件的故事结尾,引人入胜。

  关于这两个故事,在这里不说,有兴趣的朋友还是建议买这本书去看看,这本书用相当轻松的文字而又不失深沉地向我们道来C语言的各种特性与特别的用法。

  书中提到一种分析编程语言缺陷的方法,让我们能够详细的去分析各种编程语言的缺陷,即把所有的缺陷归于3类:不该做的做了(多做之过)、该做的没做(少做之过)、该做的做了但不合适(误做之过),本章也是按照这样一种分析方法来分析C语言本身存在的一些问题,由于C是一门神奇的语言,被许多平台所选用,也被大家所学习,所以了解C语言是一件相当有必要的事情,本章就是从缺陷来了解C语言。

  多做之过,就是语言中存在某些不应该存在的特性,包括容易出错的switch语句、相邻字符串常量自动连接和缺省全局作用域。

  首先说说switch语句吧,这个语句在多条件的时候使用率还是相当高的,相比大量if语句,我还是比较倾向于它的。switch语句的一般形式如下:

   switch(表达式)
  {
    case 常量表达式1:语句1; break;
    ....
    case 常量表达式n:语句n; break;
    default:语句;break;
  }

  每个case结构由3个部分组成,关键字case;其后的常量表达式;以及后面的冒号,当表达式的值与case后面的常量表达式匹配时,case后面的语句就会执行,否则执行default后面的语句,default都可以出现在case列表出现的任何位置,如果没有default语句,那么switch语句就什么也不做,你不要指望它会提醒你它什么都没做。在C语言中,几乎从来不进行运行时错误检查——对进行解引用操作的指针进行有效性检查大概是唯一的例外,这是因为运行时检查与C语言的设计理念相违背,按照C语言的理念,程序员应该知道自己在干什么,而且保证自己的所作所为是正确的。switch的另一个问题是它内部的任何语句都可以加上标签,并在执行时跳转到那里,作者给出了一个例子,那就是当你的default语句写错的时候,比如把l字母写成了数字1,看起来很像对吧defau1t,不过功能可是大不相同,这意味着如果表达式不匹配任何常量表达式时它将什么也不干,因为没有default语句啊,然而即使这样,编译器也无法检查出错误来。当然switch语句里最大的问题还不是这个,而是它不会在每个case语句执行完毕后自动跳出,如果你不使用break语句来跳出,它将一直执行下去,在《C与指针》描述switch语句时有一句话我觉得非常合适,那就是case语句只是确认进入switch语句的入口,如果你不使用break语句,那么出口都是在swtich语句的右花括号那里,作者还举了一个利用switch语句这种特性的例子,用来计算程序输入中字符、单词和行的个数,有趣的是,这个例子正是需要switch语句一直执行下去而不是遇到一个case就退出,有兴趣的朋友可以参考《C与指针》第四章的内容。当然,如果在这种情况下要使用switch语句的特性,那么一句注释"FALL THRU"是必不可少的,它会告诉你,我就是要利用这个特性,我不需要break语句,不过,在绝大多数情况下是不会需要这种特性的。以上就是switch语句存在的三个主要问题。

  其次,相邻字符串常量的自动合并这个约定也会带来一些问题。在printf的使用中,这是一个优点,因为你不用担心要输出的字符串有多长,你可以放心的用双引号包括每一行的内容,反正它会自动合并,比方说

  printf("A second favorite children's book"

    "is Thoms the tank engine and the Naughty Enginedriver who"

    "tied down thomas's boiler safety value");

这个printf语句会自动连接三个行,这可以使每一行的代码看起来简洁而又完整,不过,你该担心的是下列情况:

  char *available_resouces[] = {

    "color monitor",

    "big disk"

    "Cray"  /*少了一个逗号*/

    "on-line drawing routhines",

    ...

在这种情况下我们都知道,由于数组大小的缺省,而少了逗号会使两个字符串常量自动连接,所以在编译器看来,这并不是一个错误,它也就不会提示你,而程序可能会莫名其妙的运行,打印"Crayon-line drawing routhines“或是修改其他变量,因为字符串数目比预期少了一个。

  缺省可见性这个问题主要体现在全局函数的定义上,我们知道在声明函数的时候,如果没有任何关键字限制,那么会被自动定义为全局函数,除非你加上static关键字,才能限制对这个函数的访问。事实上,几乎所有人都没有在函数名前添加存储类型说明符的习惯,所以绝大多数函数都是全局可见的,然而,根据实际经验,这个缺省的全局可见性多次被证明是个错误。软件对象在大多数情况下应该缺省的采用有限可见性,当程序员需要让它全局可见时,应该采用显式的手段,原因在于这种大范围的全局可见性会与C语言的另一个特性产生影响,也就是用户编写和库函数同名的函数并取而代之的行为。这也说明了在C语言中,对信息可见性的选择很有限,要么是extern,意味着整个库的所有对象都可见,要么是static,对其他文件都不可见。

  所谓”误作之过“,就是语言中有误导性质或是不适当的特性,这些特性跟C语言的简洁性有关,有些则与操作符的优先级有关。

  C语言存在的其中一个问题就是它太简洁了,仅增加、修改或删除一个字符就会使原先的程序变成另外一个仍然有效但全然不同的程序,这就意味着,如果你在一个小问题上出了一点问题,那么编译器是不会检查出来提示你的,因为你的程序仍然有效。当然,还造成一个问题,那就是很多符号同时具有好几种意思,你要直到它到底是什么意思,还要根据上下文来,这一点尤其体现在作用域上。比方说static关键字就曾经令我疑惑,它有时候表示静态变量,有时候又表示内部链接属性,那么它到底代表什么呢?正确的答案是这样的,在函数内部,表示静态变量,当表示函数时,代表内部链接属性。同样的extern关键字也是这样,在缺省可见性已经提到,extern的外部链接属性不应该作为缺省属性。还有&操作符,既表示取地址操作符,又表示按位与操作,同样*操作符也有多种含义,最明显的、用法最多的操作符可能还是要数()操作符了,它们无处不在。一个符号所表达的意思越多,编译器就越难检测到这个符号在你的使用中所存在的异常情况。

  另外在操作符的优先级上,我完全能够感同身受,初学C语言,甚至在学完C语言很久一段时间之内,我都没有真正的完全搞清楚过操作符的优先级,凭感觉用吧,一般来说结果都是错的,不过用多了,可能也就会了。还记得->这个操作符在结构指针中的使用吗,我们知道->这个操作符是对一个结构成员进行解引用,它所代表的意思p->f也就相当于(*p).f,不过千万别忘了添加括号哦,因为”."操作符的优先级大于"*",这个问题也是导致->操作符出现的原因之一,类似的还有很多,比如[]的优先级高于*,int *p[]这个表达式呢代表p是一个元素为int指针的数组,而不是说p是个指向int数组的指针哦。不过在多年前,Dennis Ritchie解释了这些不正常的情况是如何由于历史的偶然原因而产生的,最大的原因还是,如果现在把它们更改过来的话,现有的大量代码都可能出现问题。

  最后,少做之过的特性就是语言应该提供但未提供的特性,如标准参数处理以及把lint程序错误的从编译器中分离出来。

  标准参数处理这个问题不管是在UNIX还是在C语言中都没有得到好好的处理,因为参数与文件名,程序是分不清楚的。其中一个例子就是在在UNIX中创建一个文件,文件名以’-‘连字符开头,然后却发现无法用rm命令把连字符去掉,这就是它分不清文件名与参数的影响,书中还给出一个有趣的实例——关于在1990年以前给“用户名的第二个字母是f的用户”发邮件,那么他将收不到,进一步让我们理解分不清参数与文件名的影响。

  而lint程序,甚至现在好多使用C语言的人都没有听过,在早期的C语言中,语言设计者作出了明确的规定——把编译器中所有的语义检查措施全部分离出来,错误检查由一个单独的程序完成,这个程序被称为“lint”,在省掉lint之后,编译器可以做得更小,更快而且更简单,所以理所当然的,它被去掉了,不过,所付出的代价是,代码中悄悄混入了大量的Bug和不可靠的编码风格,许多程序员缺省情况下在每次编译中并不使用lint。在书中给出了一些实例,是一些程序员在写代码的过程中容易犯得错误而编译器又检查不出,如果使用lint程序,则可以全部检查出来,所以作者大力推荐使用lint程序作为检查。

  下面,来介绍一下这个lint程序吧。

  lint程序不但可以检查出可移植性问题,而且可以检查出那些虽然可移植并且完全合乎语法但却很可能是错误的特性,lint程序会产生一系列程序员有必要从头到尾仔细阅读的诊断信息。

  这是lint程序的系统版本:

  UNIX系统 在UNIX系统中,可自动获得lint,它是一个标准的UNIX工具。
  Linux系统 在Linux各种发行版中,使用lint的版本是GNU下的Splint(前身是LClint)
  Windows 在Windows系统中,从第三方获得的lint工具的名称是PC lint以及Splint
在这里,由于我使用的是Linux,所以介绍一下Linux中lint的使用。
首先安装splint工具:
sudo apt install splint

  然后假定你要检查的文件是main.c

splint main.c

  其中main.c中代码如下所示,使用了switch语句来测试:

#include <stdio.h>int main(void)
{int x;scanf("%d",&x);switch(x){case 3:printf("4\n");case 4:printf("4\n");}return 0;
}

  如果是直接gcc main.c,那么不会有任何提示,使用splint程序之后,它显示了这些文本:

Splint 3.1.2 --- 03 May 2009main.c: (in function main)
main.c:6:5: Return value (type int) ignored: scanf("%d", &x)Result returned by function call is not used. If this is intended, can castresult to (void) to eliminate message. (Use -retvalint to inhibit warning)
main.c:10:10: Fall through case (no preceding break)Execution falls through from the previous case (use /*@fallthrough@*/ to markfallthrough cases). (Use -casebreak to inhibit warning)Finished checking --- 2 code warnings

  显而易见的是,它给出了两条提示,一条是说你的scanf语句的返回值并没有用,另一条就是switch语句没有break语句,并且提示你,如果确实不需要break语句,请用/*fallthrough*/把它注释出来。

  所以,多用lint程序来检查你的程序吧,说不定会给你一个惊喜。

                 

转载于:https://www.cnblogs.com/monster-prince/p/6207683.html

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

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

相关文章

数组与指针的恩怨

1、数组的本质 &#xff08;1&#xff09;、一种构造类型&#xff0c;&#xff08;2&#xff09;、相同类型的连续分配内存&#xff0c;&#xff08;3&#xff09;、数组的大小为sizeof(type)*array_size&#xff08;模子type[ ]大小&#xff09;,&#xff08;4&#xff09;、数…

path.join 和 path.resolve的区别

path.join path.join() 方法使用平台特定的分隔符把全部给定的 path 片段连接到一起&#xff0c;并规范化生成的路径。 path.join([...paths]);...paths string类型 path.join(__dirname, ./02art-template.js); // C:\Users\liangliang17\Desktop\Node_study\Node\5.path\02…

[转载]如何将Putty生成的PrivateKey转换为SecureCRT所需的PublicKey

为什么80%的码农都做不了架构师&#xff1f;>>> 首先说明&#xff0c;标题不一定准确&#xff0c;因为盆地并未详细了解清楚这里的公钥、私钥机制&#xff0c;只是根据以前的印象有个大概的理解&#xff0c;且最终以解决问题为主要目的&#xff0c;并未深究。标题的…

Linux c 算法与数据结构--双向链表

链表是linux c中非常重要的数据结构&#xff0c;双向链表与单向链表的区别&#xff0c;是它每个节点有两个指针域&#xff0c;分别指向该节点的前一个节点与后一个节点&#xff1b; 而链表的操作主要是查询、插入、删除、遍历等&#xff0c;下面来看一个双向链表&#xff0c;主…

执行shell出现bad interpreter

执行shell出现bad interpreter:No such file or directory linux执行shell出现bad interpreter:No such file or directory的原因是文件格式的问题。这个文件是在Windows下编写的。换行的方式与Unix不一样&#xff0c;但是在VI下面如果不Set一下又完全看不出来。 解决方法&…

exports、module.exports和export、export default到底是咋回事

前言 难得有空&#xff0c;今天开始重新规范的学习一下node编程。 但是引入模块我看到用 require的方式&#xff0c;再联想到咱们的ES6各种export 、export default。 阿西吧&#xff0c;头都大了.... 头大完了&#xff0c;那我们坐下先理理他们的使用范围。 require: node …

linux自动备份网站和数据库,到另外服务器上,为当前用户创建定时任务

2019独角兽企业重金招聘Python工程师标准>>> 两台服务器111&#xff0c;和117服务器,每天完成111服务器上网站和数据库自动备份到117服务器上 1&#xff1a;我的111服务器上是当前用户&#xff1a;sx A: 查看当前用户的计划任务&#xff1a;crontab -l是查看当前…

自定义控件三部曲之动画篇(一)——alpha、scale、translate、rotate、set的xml属性及用法...

前言&#xff1a;这几天做客户回访&#xff0c;感触很大&#xff0c;用户只要是留反馈信息&#xff0c;总是一种恨铁不成钢的心态&#xff0c;想用你的app&#xff0c;却是因为你的技术问题&#xff0c;让他们不得不放弃&#xff0c;而你一个回访电话却让他们尽释前嫌&#xff…

Linux c 算法与数据结构--栈

前段时间写了双向链表&#xff0c;现在写个栈&#xff0c;写之前&#xff0c;先简单介绍链表 队列  栈的区别&#xff1a; 链表&#xff0c;队列&#xff0c;堆栈的区别 1、栈是个有底的口袋&#xff0c;像袜子。 队列是没底的口袋&#xff0c;像通心粉。 所以&#xff1a;栈…

关于sass(scss)、less、postcss、stylus等的用法与区别

一. Sass/Scss、Less、stylus是什么? 它们都是css预处理器。css预处理器的概念&#xff1a;CSS预处理器用一种专门的编程语言&#xff0c;进行Web页面样式设计&#xff0c;然后再编译成正常的CSS文件&#xff0c;以供项目使用。CSS预处理器为CSS增加一些编程的特性&#xff0…

计算机点滴

CD&#xff0c;VCD&#xff0c;DVD的区别CD:纯音乐 VCD&#xff1a;影视初级光盘 DVD&#xff1a;高清晰影视光盘 EVD&#xff1a;高清晰数码影视 MPEG4&#xff1a;压缩高密影视光盘 MP3&#xff1a;压缩纯音乐1.容量大小不同&#xff1a;DVD可以装更多的内容。CD与DVD差别在于…

Linux C 算法与数据结构 --二叉树

头文件BiTree.h [cpp] view plaincopy typedef int Item; typedef struct node { struct node * lchild; struct node * rchild; Item data; }BiTNode,*BiTree; /*构造一棵新的二叉树*/ BiTree InitBiTree(BiTNode *root); /*生成节点*/ …

Spring 实现数据库读写分离

Spring 实现数据库读写分离 现在大型的电子商务系统&#xff0c;在数据库层面大都采用读写分离技术&#xff0c;就是一个Master数据库&#xff0c;多个Slave数据库。Master库负责数据更新和实时数据查询&#xff0c;Slave库当然负责非实时数据查询。因为在实际的应用中&#xf…

vue 3.x 中使用ele-image时相对路径的图片加载失败

参考文档&#xff1a; https://element.eleme.cn/#/zh-CN/component/installation 环境: Mac OS X 10.12 [zcmele 2]$node -v v12.6.0 [zcmele 3]$npm -v 6.9.0 [zcmele 4]$cnpm -v cnpm6.1.0 (/usr/local/lib/node_modules/cnpm/lib/parse_argv.js) npm6.10.2 (/usr/local/li…

JavaScript函数绑定

一个简单的函数绑定 在JavaScript与DOM交互中经常需要使用函数绑定&#xff0c;定义一个函数然后将其绑定到特定DOM元素或集合的某个事件触发程序上&#xff0c;绑定函数经常和回调函数及事件处理程序一起使用&#xff0c;以便把函数作为变量传递的同时保留代码执行环境。 <…

ie6兼容问题汇总

这几天在查找和解决网页在ie6下的兼容性问题花了我不少的时间&#xff0c;参考了网上的一些解决方法和自己做出来比较有效果的给大家参考一下&#xff0c;也方便我日后再用到&#xff1a; 1.IE的cache设置为Every visit to the page&#xff0c;而不是默认的Automatically。基本…

Linux C 数据结构---线性表

数据结构指的是数据元素及数据元素之间的相互关系&#xff0c;包含下面三方面的内容&#xff1a; 其中&#xff0c;线性表是最基本、最简单、也是最常用的一种数据结构。线性表中数据元素之间的关系是一对一的关系&#xff0c;即除了第一个和最后一个数据元素之外&#xff0c;其…

Postman发送请求时带上登录信息

正常情况下&#xff0c;没有登录验证等公共接口&#xff0c;用postman进行get或post请求都很方便&#xff0c;加上相应的参数就行。 但是对于某些接口&#xff0c;可能需要先登录后才能请求&#xff0c;这时如果按正常的思路请求&#xff0c;可能就会被拦截了。 对于这种情况…

Chrome跨域问题

2019独角兽企业重金招聘Python工程师标准>>> 在chrome图标&#xff0c;右键--->属性 --->目标 路径末尾添加 “--disable-web-security” 重启即可 转载于:https://my.oschina.net/u/861562/blog/152171

解决安装mysql的”A Windows service with the name MySQL already exists.“问题

如果以前安装过mysql&#xff0c;卸载重装&#xff0c;很可能会碰到”A Windows service with the name MySQL already exists.“这样的提示。即服务已经存在。我们可以在window任务管理器----服务中查看&#xff0c;发现确实存在&#xff0c;没有卸载干净。 解决这个问题&…