编写高质量代码:改善C程序代码的125个建议

编写高质量代码:改善C程序代码的125个建议

暂无评价综合评分的显示会考虑用户真实性等多项因素,每部作品出现综合评分的时间不定。

作品简介

C语言因为既具有高级语言特性,又具有汇编语言特性,所以它是近二十几年来使用最为广泛、生命力最强的编程语言。无论是操作系统、嵌入式系统、普通应用软件,还是移动智能设备开发,它都能够很好地胜任,是公认的最强大的语言。但是,它也是广大程序员认为最难的一门语言,大多数人都停留在“入门”阶段。作为一位C程序员,如何才能编写出健壮、优雅、高质量的代码?

本书作者结合自己在该领域十几年的钻研和实践,从以下几个大的方面梳理了经常困扰“入门者”的几大类问题:

基础数据类型问题:如数据取值范围、整数溢出与回绕、浮点数精度、数据类型转换的范围检查等;

数组与指针问题:指针与地址、野指针、空(null)指针、NULL指针、void指针、多级指针、指针函数与函数指针、数组越界与缓冲区溢出等;

内存管理问题:内存分配、内存释放、内存越界与内存泄漏等;

字符与字符串问题:串拷贝与内存拷贝、内存重叠与溢出、字符串查找等;

高效设计问题:表达式设计、算法设计与函数设计、内联函数与宏的取舍等;

其他杂项问题:信号处理、文件系统、断言与异常处理、内嵌汇编的使用等。

作者: 马伟

作品目录

  1. 前言
  2. 为什么写作本书
  3. 如何阅读本书
  4. 资源及勘误
  5. 特别鸣谢
  6. 第1章 数据,程序设计之根本
  7. 建议1:认识ANSI C
  8. 建议2:防止整数类型产生回绕与溢出
  9. 建议2-1:char类型变量的值应该限制在signed char与unsigned char的交集范围内
  10. 建议2-2:使用显式声明为signed char或unsigned char的类型来执行算术运算
  11. 建议2-3:使用rsize_t或size_t类型来表示一个对象所占用空间的整数值单位
  12. 建议2-4:禁止把size_t类型和它所代表的真实类型混用
  13. 建议2-5:小心使用无符号类型带来的陷阱
  14. 建议2-6:防止无符号整数回绕
  15. 建议2-7:防止有符号整数溢出
  16. 建议3:尽量少使用浮点类型
  17. 建议3-1:了解IEEE 754浮点数
  18. 建议3-2:避免使用浮点数进行精确计算
  19. 建议3-3:使用分数来精确表达浮点数
  20. 建议3-4:避免直接在浮点数中使用“==”操作符做相等判断
  21. 建议3-5:避免使用浮点数作为循环计数器
  22. 建议3-6:尽量将浮点运算中的整数转换为浮点数
  23. 建议4:数据类型转换必须做范围检查
  24. 建议4-1:整数转换为新类型时必须做范围检查
  25. 建议4-2:浮点数转换为新类型时必须做范围检查
  26. 建议5:使用有严格定义的数据类型
  27. 建议6:使用typedef来定义类型的新别名
  28. 建议6-1:掌握typedef的4种应用形式
  29. 建议6-2:小心使用typedef带来的陷阱
  30. 建议6-3:typedef不同于#define
  31. 建议7:变量声明应该力求简洁
  32. 建议7-1:尽量不要在一个声明中声明超过一个的变量
  33. 建议7-2:避免在嵌套的代码块之间使用相同的变量名
  34. 建议8:正确地选择变量的存储类型
  35. 建议8-1:定义局部变量时应该省略auto关键字
  36. 建议8-2:慎用extern声明外部变量
  37. 建议8-3:不要混淆static变量的作用
  38. 建议8-4:尽量少使用register变量
  39. 建议9:尽量不要在可重入函数中使用静态(或全局)变量
  40. 建议10:尽量少使用全局变量
  41. 建议11:尽量使用const声明值不会改变的变量
  42. 第2章 保持严谨的程序设计,一切从表达式开始做起
  43. 建议12:尽量减少使用除法运算与求模运算
  44. 建议12-1:用倒数相乘来实现除法运算
  45. 建议12-2:使用牛顿迭代法求除数的倒数
  46. 建议12-3:用减法运算来实现整数除法运算
  47. 建议12-4:用移位运算实现乘除法运算
  48. 建议12-5:尽量将浮点除法转化为相应的整数除法运算
  49. 建议13:保证除法和求模运算不会导致除零错误
  50. 建议14:适当地使用位操作来提高计算效率
  51. 建议14-1:尽量避免对未知的有符号数执行位操作
  52. 建议14-2:在右移中合理地选择0或符号位来填充空出的位
  53. 建议14-3:移位的数量必须大于等于0且小于操作数的位数
  54. 建议14-4:尽量避免在同一个数据上执行位操作与算术运算
  55. 建议15:避免操作符混淆
  56. 建议15-1:避免“=”与“==”混淆
  57. 建议15-2:避免“|”与“||”混淆
  58. 建议15-3:避免“&”与“&&”混淆
  59. 建议16:表达式的设计应该兼顾效率与可读性
  60. 建议16-1:尽量使用复合赋值运算符
  61. 建议16-2:尽量避免编写多用途的、太复杂的复合表达式
  62. 建议16-3:尽量避免在表达式中使用默认的优先级
  63. 第3章 程序控制语句应该保持简洁高效
  64. 建议17:if语句应该尽量保持简洁,减少嵌套的层数
  65. 建议17-1:先处理正常情况,再处理异常情况
  66. 建议17-2:避免“悬挂”的else
  67. 建议17-3:避免在if/else语句后面添加分号“;”
  68. 建议17-4:对深层嵌套的if语句进行重构
  69. 建议18:谨慎0值比较
  70. 建议18-1:避免布尔型与0或1进行比较
  71. 建议18-2:整型变量应该直接与0进行比较
  72. 建议18-3:避免浮点变量用“==”或“!=”与0进行比较
  73. 建议18-4:指针变量应该用“==”或“!=”与NULL进行比较
  74. 建议19:避免使用嵌套的“?:”
  75. 建议20:正确使用for循环
  76. 建议20-1:尽量使循环控制变量的取值采用半开半闭区间写法
  77. 建议20-2:尽量使循环体内工作量达到最小化
  78. 建议20-3:避免在循环体内修改循环变量
  79. 建议20-4:尽量使逻辑判断语句置于循环语句外层
  80. 建议20-5:尽量将多重循环中最长的循环放在最内层,最短的循环放在最外层
  81. 建议20-6:尽量将循环嵌套控制在3层以内
  82. 建议21:适当地使用并行代码来优化for循环
  83. 建议22:谨慎使用do/while与while循环
  84. 建议22-1:无限循环优先选用for(;;),而不是while(1)
  85. 建议22-2:优先使用for循环替代do/while与while循环
  86. 建议23:正确地使用switch语句
  87. 建议23-1:不要忘记在case语句的结尾添加break语句
  88. 建议23-2:不要忘记在switch语句的结尾添加default语句
  89. 建议23-3:不要为了使用case语句而刻意构造一个变量
  90. 建议23-4:尽量将长的switch语句转换为嵌套的switch语句
  91. 建议24:选择合理的case语句排序方法
  92. 建议24-1:尽量按照字母或数字顺序来排列各条case语句
  93. 建议24-2:尽量将情况正常的case语句排在最前面
  94. 建议24-3:尽量根据发生频率来排列各条case语句
  95. 建议25:尽量避免使用goto语句
  96. 建议26:区别continue与break语句
  97. 第4章 函数同样需要保持简洁高效
  98. 建议27:理解函数声明
  99. 建议28:理解函数原型
  100. 建议29:尽量使函数的功能单一
  101. 建议30:避免把没有关联的语句放在一个函数中
  102. 建议31:函数的抽象级别应该在同一层次
  103. 建议32:尽可能为简单功能编写函数
  104. 建议33:避免多段代码重复做同一件事情
  105. 建议34:尽量避免编写不可重入函数
  106. 建议34-1:避免在函数中使用static局部变量
  107. 建议34-2:避免函数返回指向静态数据的指针
  108. 建议34-3:避免调用任何不可重入函数
  109. 建议34-4:对于全局变量,应通过互斥信号量(即P、V操作)或者中断机制等方法来保证函数的线程安全
  110. 建议34-5:理解可重入函数与线程安全函数之间的关系
  111. 建议35:尽量避免设计多参数函数
  112. 建议35-1:没有参数的函数必须使用void填充
  113. 建议35-2:尽量避免在非调度函数中使用控制参数
  114. 建议35-3:避免将函数的参数作为工作变量
  115. 建议35-4:使用const防止指针类型的输入参数在函数体内被意外修改
  116. 建议36:没有返回值的函数应声明为void类型
  117. 建议37:确保函数体的“入口”与“出口”安全性
  118. 建议37-1:尽量在函数体入口处对参数做有效性检查
  119. 建议37-2:尽量在函数体出口处对return语句做安全性检查
  120. 建议38:在调用函数时,必须对返回值进行判断,同时对错误的返回值还要有相应的错误处理
  121. 建议39:尽量减少函数本身或者函数间的递归调用
  122. 建议40:尽量使用inline内联函数来替代#define宏
  123. 第5章 不会使用指针的程序员是不合格的
  124. 建议41:理解指针变量的存储实质
  125. 建议42:指针变量必须初始化
  126. 建议43:区别“int*p=NULL”和“*p=NULL”
  127. 建议44:理解空(null)指针与NULL指针
  128. 建议44-1:区别空(null)指针与NULL指针的概念
  129. 建议44-2:用NULL指针终止对递归数据结构的间接引用
  130. 建议44-3:用NULL指针作函数调用失败时的返回值
  131. 建议44-4:用NULL指针作警戒值
  132. 建议44-5:避免对NULL指针进行解引用
  133. 建议45:谨慎使用void指针
  134. 建议45-1:避免对void指针进行算术操作
  135. 建议45-2:如果函数的参数可以是任意类型指针,应该将其参数声明为void*
  136. 建议46:避免使用指针的长度确定它所指向类型的长度
  137. 建议47:避免把指针转换为对齐要求更严格的指针类型
  138. 建议48:避免将一种类型的操作符应用于另一种不兼容的类型
  139. 建议49:谨慎指针与整数之间的转换
  140. 建议50:区别“const int*p”与“int*const p”
  141. 建议51:深入理解函数参数的传递方式
  142. 建议51-1:理解函数参数的传递过程
  143. 建议51-2:掌握函数的参数传递方式
  144. 建议51-3:如果函数的参数是指针,避免用该指针去申请动态内存
  145. 建议51-4:尽量避免使用可变参数
  146. 第6章 数组并非指针
  147. 建议52:理解数组的存储实质
  148. 建议52-1:理解数组的存储布局
  149. 建议52-2:理解&a[0]和&a的区别
  150. 建议52-3:理解数组名a作为右值和左值的区别
  151. 建议53:避免数组越界
  152. 建议53-1:尽量显式地指定数组的边界
  153. 建议53-2:对数组做越界检查,确保索引值位于合法的范围之内
  154. 建议53-3:获取数组的长度时不要对指针应用sizeof操作符
  155. 建议54:数组并非指针
  156. 建议55:理解数组与指针的可交换性
  157. 建议56:禁止将一个指向非数组对象的指针加上或减去一个整数
  158. 建议57:禁止对两个并不指向同一个数组的指针进行相减或比较
  159. 建议58:若结果值并不引用合法的数组元素,不要将指针加上或减去一个整数
  160. 建议59:细说缓冲区溢出
  161. 建议60:区别指针数组和数组指针
  162. 建议61:深入理解数组参数
  163. 第7章 结构、位域和枚举
  164. 建议62:结构体的设计要遵循简单、单一原则
  165. 建议62-1:尽量使结构体的功能单一
  166. 建议62-2:尽量减小结构体间关系的复杂度
  167. 建议62-3:尽量使结构体中元素的个数适中
  168. 建议62-4:合理划分与改进结构体以提高空间效率
  169. 建议63:合理利用结构体内存对齐原理来提高程序效率
  170. 建议64:结构体的长度不一定等于各个成员的长度之和
  171. 建议65:避免在结构体之间执行逐字节比较
  172. 建议66:谨慎使用位域
  173. 建议67:谨慎使用枚举
  174. 建议68:禁止在位域成员上调用offsetof宏
  175. 建议69:深入理解结构体数组和结构体指针
  176. 第8章 字符与字符串
  177. 建议70:不要忽视字符串的null('\0')结尾符
  178. 建议70-1:正确认识字符数组和字符串
  179. 建议70-2:字符数组必须能够同时容纳字符数据和null结尾符
  180. 建议70-3:谨慎字符数组的初始化
  181. 建议71:尽量使用const指针来引用字符串常量
  182. 建议72:区别strlen函数与sizeof运算符
  183. 建议73:在使用不受限制的字符串函数时,必须保证结果字符串不会溢出内存
  184. 建议73-1:避免字符串拷贝发生溢出
  185. 建议73-2:区别串拷贝strcpy与内存拷贝memcpy
  186. 建议73-3:避免strcpy与memcpy函数内存重叠
  187. 建议73-4:区别字符串比较与内存比较
  188. 建议73-5:避免strcat函数发生内存重叠与溢出
  189. 建议74:谨慎strtok函数的不可重入性
  190. 建议75:掌握字符串查找技术
  191. 建议75-1:使用strchr与strrchr函数查找单个字符
  192. 建议75-2:使用strpbrk函数查找多个字符
  193. 建议75-3:使用strstr函数查找一个子串
  194. 建议75-4:区别strspn与strcspn函数
  195. 第9章 文件系统
  196. 建议76:谨慎使用printf和scanf函数
  197. 建议77:谨慎文件打开操作
  198. 建议77-1:正确指定fopen的mode参数
  199. 建议77-2:必须检查fopen函数的返回值
  200. 建议77-3:尽量避免重复打开已经被打开的文件
  201. 建议77-4:区别fopen与fopen_s函数
  202. 建议77-5:区别fopen与freopen函数
  203. 建议78:文件操作完成后必须关闭
  204. 建议79:正确理解EOF宏
  205. 建议80:尽量使用feof和ferror检测文件结束和错误
  206. 建议81:尽量使用fgets替换gets函数
  207. 建议82:尽量使用fputs替换puts函数
  208. 建议83:合理选择单个字符读写函数
  209. 建议84:区别格式化读写函数
  210. 建议84-1:区别printf/scanf、fprintf/fscanf和sprintf/sscanf
  211. 建议84-2:尽量使用snprintf替代sprintf函数
  212. 建议84-3:区别vprintf/vscanf、vfprintf/vfscanf、vsprintf/vsscanf和vsnprintf
  213. 建议85:尽量使用fread与fwrite函数来读写二进制文件
  214. 建议86:尽量使用fseek替换rewind函数
  215. 建议87:尽量使用setvbuf替换setbuf函数
  216. 建议88:谨慎remove函数删除已打开的文件
  217. 建议89:谨慎rename函数重命名已经存在的文件
  218. 第10章 预处理器
  219. 建议90:谨慎宏定义
  220. 建议90-1:在使用宏定义表达式时必须使用完备的括号
  221. 建议90-2:尽量消除宏的副作用
  222. 建议90-3:避免使用宏创建一种“新语言”
  223. 建议91:合理地选择函数与宏
  224. 建议92:尽量使用内联函数代替宏
  225. 建议93:掌握预定义宏
  226. 建议94:谨慎使用“#include”
  227. 建议94-1:区别“#include<filename.h>”与“#include"filename.h"”
  228. 建议94-2:必须保证头文件名称的唯一性
  229. 建议95:掌握条件编译指令
  230. 建议95-1:使用“#ifndef/#define/#endif”防止头文件被重复引用
  231. 建议95-2:使用条件编译指令实现源代码的部分编译
  232. 建议95-3:妙用“defined”
  233. 建议96:尽量避免在一个函数块中单独使用“#define”或“#undef”
  234. 第11章 断言与异常处理
  235. 建议97:谨慎使用断言
  236. 建议97-1:尽量利用断言来提高代码的可测试性
  237. 建议97-2:尽量在函数中使用断言来检查参数的合法性
  238. 建议97-3:避免在断言表达式中使用改变环境的语句
  239. 建议97-4:避免使用断言去检查程序错误
  240. 建议97-5:尽量在防错性程序设计中使用断言来进行错误报警
  241. 建议97-6:用断言保证没有定义的特性或功能不被使用
  242. 建议97-7:谨慎使用断言对程序开发环境中的假设进行检查
  243. 建议98:谨慎使用errno
  244. 建议98-1:调用errno之前必须先将其清零
  245. 建议98-2:避免重定义errno
  246. 建议98-3:避免使用errno检查文件流错误
  247. 建议99:谨慎使用函数的返回值来标志函数是否执行成功
  248. 建议100:尽量避免使用goto进行出错跳转
  249. 建议101:尽量避免使用setjmp与longjmp组合
  250. 第12章 内存管理
  251. 建议102:浅谈程序的内存结构
  252. 建议103:浅谈堆和栈
  253. 建议104:避免错误分配内存
  254. 建议104-1:对内存分配函数的返回值必须进行检查
  255. 建议104-2:内存资源的分配与释放应该限定在同一模块或者同一抽象层内进行
  256. 建议104-3:必须对内存分配函数的返回指针进行强制类型转换
  257. 建议104-4:确保指针指向一块合法的内存
  258. 建议104-5:确保为对象分配足够的内存空间
  259. 建议104-6:禁止执行零长度的内存分配
  260. 建议104-7:避免大型的堆栈分配
  261. 建议104-8:避免内存分配成功,但并未初始化
  262. 建议105:确保安全释放内存
  263. 建议105-1:malloc等内存分配函数与free必须配对使用
  264. 建议105-2:在free之后必须为指针赋一个新值
  265. 建议106:避免内存越界
  266. 建议106-1:避免数组越界
  267. 建议106-2:避免sprintf、vsprintf、strcpy、strcat与gets越界
  268. 建议106-3:避免memcpy与memset函数长度越界
  269. 建议106-4:避免忽略字符串最后的'\0'字符而导致的越界
  270. 建议107:避免内存泄漏
  271. 建议108:避免calloc参数相乘的值超过size_t表示的范围
  272. 第13章 信号处理
  273. 建议109:理解信号
  274. 建议110:尽量使用sigaction替代signal
  275. 建议111:避免在信号处理函数内部访问或修改共享对象
  276. 建议112:避免以递归方式调用raise函数
  277. 第14章 了解C11标准
  278. 建议113:谨慎使用_Generic
  279. 建议114:尽量使用gets_s替换gets函数
  280. 建议115:尽量使用带边界检查的字符串操作函数
  281. 建议116:了解C11多线程编程
  282. 建议117:使用静态断言_Static_assert执行编译时检查
  283. 建议118:使用_Noreturn标识不返回值的函数
  284. 第15章 保持良好的设计
  285. 建议119:避免错误地变量初始化
  286. 建议120:谨慎使用内联函数
  287. 建议121:避免在函数内定义占用内存很大的局部变量
  288. 建议122:谨慎设计函数参数的顺序和个数
  289. 建议123:谨慎使用标准函数库
  290. 建议124:避免不必要的函数调用
  291. 建议125:谨慎程序中嵌入汇编代码
载入中

大家都喜欢