天天看點

《編寫高品質代碼:改善c程式代碼的125個建議》—— 導讀

《編寫高品質代碼:改善c程式代碼的125個建議》—— 導讀

https://yqfile.alicdn.com/e0017382e024078608cb6940e592db531b840ddf.png" >

前  言

為什麼寫作本書

衆所周知,c語言是一門既具有進階語言特點,又有彙編語言特點的通用計算機程式設計語言,無論是作業系統(如microsoft windows、mac os x、linux和unix等)、嵌入式系統與普通應用軟體,還是目前流行的移動智能裝置開發,随處都可以看見它依然矯健的身影。它能夠輕松地應用于各類層次的開發中,從裝置驅動程式和作業系統元件到大規模應用程式,它都能夠很好地勝任。毋庸置疑,它是二十幾年來使用最為廣泛、生命力最強的程式設計語言,它的設計思想也影響了衆多後來的程式設計語言,例如c++、objective-c、java、c#等。

盡管c語言有着悠久的曆史和廣泛的使用場景,但它依舊讓大部分計算機程式設計人員望而生畏,相信絕大多數讀者也還停留在“入門者”這個階段。所謂“入門者”指的是已經可以簡單使用c語言編寫普通應用程式,但是卻不明白如何編寫高品質代碼的人。面對這樣的實際情況,在準備編寫本書之前,一連串的問題深深地映入筆者的腦海:到底什麼樣的程式設計書籍才能夠幫助“入門者”快速進階?面對市面上衆多的優秀c語言程式設計書籍,編寫本書的價值何在?怎樣的内容才能夠與衆不同?

帶着這一連串的問題,筆者開始回顧自己這些年的開發生涯,發現如下幾類問題經常困擾“入門者”:

基礎資料類型問題:如資料取值範圍、整數溢出與回繞、浮點數精度、資料類型轉換的範圍檢查等。

數組與指針問題:指針與位址、野指針、空(null)指針、null指針、void指針、多級指針、指針函數與函數指針,以及數組越界與緩沖區溢出等。

記憶體管理問題:記憶體配置設定、記憶體釋放、記憶體越界與記憶體洩漏等。

字元與字元串問題:串拷貝與記憶體拷貝,記憶體重疊與溢出,字元串查找等。

高效設計問題:表達式設計、算法設計與函數設計,内聯函數與宏的取舍等。

其他雜項問題:信号處理、檔案系統、斷言與異常處理、内嵌彙編的使用等。

目  錄

前 言

[第1章 資料,程式設計之根本

<a href="https://yq.aliyun.com/articles/108340">建議2:防止整數類型産生回繞與溢出</a>

<a href="https://yq.aliyun.com/articles/108345">建議2-1:char類型變量的值應該限制在signed char與unsigned char的交集範圍内</a>

<a href="https://yq.aliyun.com/articles/108351">建議2-2:使用顯式聲明為signed char或unsigned char的類型來執行算術運算</a>

<a href="https://yq.aliyun.com/articles/108356">建議2-3:使用rsize_t或size_t類型來表示一個對象所占用空間的整數值機關</a>

<a href="https://yq.aliyun.com/articles/108360">建議2-4:禁止把size_t類型和它所代表的真實類型混用</a>

<a href="https://yq.aliyun.com/articles/108366">建議2-5:小心使用無符号類型帶來的陷阱</a>

<a href="https://yq.aliyun.com/articles/108375">建議2-6:防止無符号整數回繞</a>

<a href="https://yq.aliyun.com/articles/108383">建議2-7:防止有符号整數溢出</a>

<a href="https://yq.aliyun.com/articles/108390">建議3:盡量少使用浮點類型</a>

<a href="https://yq.aliyun.com/articles/108415">建議3-1:了解ieee 754浮點數</a>

<a href="https://yq.aliyun.com/articles/108420">建議3-2:避免使用浮點數進行精确計算</a>

<a href="https://yq.aliyun.com/articles/108429">建議3-3:使用分數來精确表達浮點數</a>

<a href="https://yq.aliyun.com/articles/108435">建議3-4:避免直接在浮點數中使用“==”操作符做相等判斷</a>

<a href="https://yq.aliyun.com/articles/108443">建議3-5:避免使用浮點數作為循環計數器</a>

<a href="https://yq.aliyun.com/articles/108448">建議3-6:盡量将浮點運算中的整數轉換為浮點數</a>

<a href="https://yq.aliyun.com/articles/108450">建議4:資料類型轉換必須做範圍檢查</a>

<a href="https://yq.aliyun.com/articles/108453">建議4-1:整數轉換為新類型時必須做範圍檢查</a>

<a href="https://yq.aliyun.com/articles/108458">建議4-2:浮點數轉換為新類型時必須做範圍檢查</a>

<a href="https://yq.aliyun.com/articles/108463">建議5:使用有嚴格定義的資料類型</a>

<a href="https://yq.aliyun.com/articles/108466">建議6:使用typedef來定義類型的新别名</a>

<a href="https://yq.aliyun.com/articles/108471">建議6-1:掌握typedef的4種應用形式</a>

<a href="https://yq.aliyun.com/articles/108473">建議6-2:小心使用typedef帶來的陷阱</a>

<a href="https://yq.aliyun.com/articles/108476">建議6-3:typedef不同于#define</a>

<a href="https://yq.aliyun.com/articles/108482">建議7:變量聲明應該力求簡潔</a>

<a href="https://yq.aliyun.com/articles/108486">建議7-1:盡量不要在一個聲明中聲明超過一個的變量</a>

<a href="https://yq.aliyun.com/articles/108487">建議7-2:避免在嵌套的代碼塊之間使用相同的變量名</a>

<a href="https://yq.aliyun.com/articles/108492">建議8:正确地選擇變量的存儲類型</a>

<a href="https://yq.aliyun.com/articles/108493">建議8-1:定義局部變量時應該省略auto關鍵字</a>

<a href="https://yq.aliyun.com/articles/108494">建議8-2:慎用extern聲明外部變量</a>

<a href="https://yq.aliyun.com/articles/108499">建議8-3:不要混淆static變量的作用</a>

<a href="https://yq.aliyun.com/articles/108519">建議8-4:盡量少使用register變量</a>

<a href="https://yq.aliyun.com/articles/108522">建議9:盡量不要在可重入函數中使用靜态(或全局)變量</a>

<a href="https://yq.aliyun.com/articles/108524">建議10:盡量少使用全局變量</a>

<a href="https://yq.aliyun.com/articles/108527">建議11:盡量使用const聲明值不會改變的變量</a>

[第2章 保持嚴謹的程式設計,一切從表達式開始做起

<a href="https://yq.aliyun.com/articles/108541">建議12-1:用倒數相乘來實作除法運算</a>

<a href="https://yq.aliyun.com/articles/108546">建議12-2:使用牛頓疊代法求除數的倒數</a>

<a href="https://yq.aliyun.com/articles/108548">建議12-3:用減法運算來實作整數除法運算</a>

<a href="https://yq.aliyun.com/articles/108551">建議12-4:用移位運算實作乘除法運算</a>

<a href="https://yq.aliyun.com/articles/108553">建議12-5:盡量将浮點除法轉化為相應的整數除法運算</a>

<a href="https://yq.aliyun.com/articles/108556">建議13:保證除法和求模運算不會導緻除零錯誤</a>

<a href="https://yq.aliyun.com/articles/108560">建議14:适當地使用位操作來提高計算效率</a>

<a href="https://yq.aliyun.com/articles/108564">建議14-1:盡量避免對未知的有符号數執行位操作</a>

<a href="https://yq.aliyun.com/articles/108567">建議14-2:在右移中合理地選擇0或符号位來填充空出的位</a>

<a href="https://yq.aliyun.com/articles/108569">建議14-3:移位的數量必須大于等于0且小于操作數的位數</a>

<a href="https://yq.aliyun.com/articles/108571">建議14-4:盡量避免在同一個資料上執行位操作與算術運算</a>

<a href="https://yq.aliyun.com/articles/108574">建議15:避免操作符混淆</a>

<a href="https://yq.aliyun.com/articles/108578">建議15-1:避免“=”與“==”混淆</a>

<a href="https://yq.aliyun.com/articles/108581">建議15-2:避免“|”與“||”混淆</a>

<a href="https://yq.aliyun.com/articles/108583">建議15-3:避免“&amp;”與“&amp;&amp;”混淆</a>

<a href="https://yq.aliyun.com/articles/108585">建議16:表達式的設計應該兼顧效率與可讀性</a>

<a href="https://yq.aliyun.com/articles/108587">建議16-1:盡量使用複合指派運算符</a>

<a href="https://yq.aliyun.com/articles/108590">建議16-2:盡量避免編寫多用途的、太複雜的複合表達式</a>

<a href="https://yq.aliyun.com/articles/108595">建議16-3:盡量避免在表達式中使用預設的優先級</a>

[第3章 程式控制語句應該保持簡潔高效

<a href="https://yq.aliyun.com/articles/108599">建議17-1:先處理正常情況,再處理異常情況</a>

<a href="https://yq.aliyun.com/articles/108604">建議17-2:避免“懸挂”的else</a>

<a href="https://yq.aliyun.com/articles/108608">建議17-3:避免在if/else語句後面添加分号“;”</a>

<a href="https://yq.aliyun.com/articles/108613">建議17-4:對深層嵌套的if語句進行重構</a>

[建議18:謹慎0值比較

<a href="https://yq.aliyun.com/articles/108618">建議18-2:整型變量應該直接與0進行比較</a>

<a href="https://yq.aliyun.com/articles/108621">建議18-3:避免浮點變量用“==”或“!=”與0進行比較</a>

<a href="https://yq.aliyun.com/articles/108625">建議18-4:指針變量應該用“==”或“!=”與null進行比較</a>

<a href="https://yq.aliyun.com/articles/108628">建議19:避免使用嵌套的“?:”</a>

<a href="https://yq.aliyun.com/articles/108662">建議20:正确使用for循環</a>

<a href="https://yq.aliyun.com/articles/108663">建議20-1:盡量使循環控制變量的取值采用半開半閉區間寫法</a>

<a href="https://yq.aliyun.com/articles/108664">建議20-2:盡量使循環體内工作量達到最小化</a>

<a href="https://yq.aliyun.com/articles/108666">建議20-3:避免在循環體内修改循環變量</a>

<a href="https://yq.aliyun.com/articles/108667">建議20-4:盡量使邏輯判斷語句置于循環語句外層</a>

<a href="https://yq.aliyun.com/articles/108668">建議20-5:盡量将多重循環中最長的循環放在最内層,最短的循環放在最外層</a>

<a href="https://yq.aliyun.com/articles/108670">建議20-6:盡量将循環嵌套控制在3 層以内</a>

<a href="https://yq.aliyun.com/articles/108671">建議21:适當地使用并行代碼來優化for循環</a>

[建議22:謹慎使用do/while與while循環

建議22-1:無限循環優先選用for( ; ; ),而不是while(1)

[建議23:正确地使用switch語句

建議23-1:不要忘記在case 語句的結尾添加break語句

建議23-2:不要忘記在switch語句的結尾添加default語句

建議23-3:不要為了使用case 語句而刻意構造一個變量

[建議24:選擇合理的case語句排序方法

建議24-1:盡量按照字母或數字順序來排列各條case 語句

建議24-2:盡量将情況正常的case 語句排在最前面

<a href="https://yq.aliyun.com/articles/108683">建議25:盡量避免使用goto語句</a>

<a href="https://yq.aliyun.com/articles/108687">建議26:差別continue與break語句</a>

第4章 函數同樣需要保持簡潔高效

建議27:了解函數聲明

建議28:了解函數原型

建議29:盡量使函數的功能單一

建議30:避免把沒有關聯的語句放在一個函數中

建議31:函數的抽象級别應該在同一層次

建議32:盡可能為簡單功能編寫函數

建議33:避免多段代碼重複做同一件事情

建議34:盡量避免編寫不可重入函數

建議34-1:避免在函數中使用static 局部變量

建議34-2:避免函數傳回指向靜态資料的指針

建議34-3:避免調用任何不可重入函數

建議34-4:對于全局變量,應通過互斥信号量(即p、v操作)或者中斷機制等方法來保證函數的線程安全

建議34-5:了解可重入函數與線程安全函數之間的關系

建議35:盡量避免設計多參數函數

建議35-1:沒有參數的函數必須使用void填充

建議35-2:盡量避免在非排程函數中使用控制參數

建議35-3:避免将函數的參數作為工作變量

建議35-4:使用const防止指針類型的輸入參數在函數體内被意外修改

建議36:沒有傳回值的函數應聲明為void類型

建議37:確定函數體的“入口”與“出口”安全性

建議37-1:盡量在函數體入口處對參數做有效性檢查

建議37-2:盡量在函數體出口處對return語句做安全性檢查

建議38:在調用函數時,必須對傳回值進行判斷,同時對錯誤的傳回值還要有相應的錯誤處理

建議39:盡量減少函數本身或者函數間的遞歸調用

建議40:盡量使用inline内聯函數來替代#define宏

第5章 不會使用指針的程式員是不合格的

建議41:了解指針變量的存儲實質

建議42:指針變量必須初始化

建議43:差別“int p = null” 和“p = null”

建議44:了解空(null)指針與null指針

建議44-1:差別空(null)指針與null指針的概念

建議44-2:用null指針終止對遞歸資料結構的間接引用

建議44-3:用null指針作函數調用失敗時的傳回值

建議44-4:用null指針作警戒值

建議44-5:避免對null指針進行解引用

建議45:謹慎使用void指針

建議45-1:避免對void指針進行算術操作

建議45-2:如果函數的參數可以是任意類型指針,應該将其參數聲明為void *

建議46:避免使用指針的長度确定它所指向類型的長度

建議47:避免把指針轉換為對齊要求更嚴格的指針類型

建議48:避免将一種類型的操作符應用于另一種不相容的類型

建議49:謹慎指針與整數之間的轉換

建議50:差別“const int p”與“int const p”

建議51:深入了解函數參數的傳遞方式

建議51-1:了解函數參數的傳遞過程

建議51-2:掌握函數的參數傳遞方式

建議51-3:如果函數的參數是指針,避免用該指針去申請動态記憶體

建議51-4:盡量避免使用可變參數

第6章 數組并非指針

建議52:了解數組的存儲實質

建議52-1:了解數組的存儲布局

建議52-2:了解&amp;a[0]和&amp;a的差別

建議52-3:了解數組名a作為右值和左值的差別

建議53:避免數組越界

建議53-1:盡量顯式地指定數組的邊界

建議53-2:對數組做越界檢查,確定索引值位于合法的範圍之内

建議53-3:擷取數組的長度時不要對指針應用sizeof操作符

建議54:數組并非指針

建議55:了解數組與指針的可交換性

建議56:禁止将一個指向非數組對象的指針加上或減去一個整數

建議57:禁止對兩個并不指向同一個數組的指針進行相減或比較

建議58:若結果值并不引用合法的數組元素,不要将指針加上或減去一個整數

建議59:細說緩沖區溢出

建議60:差別指針數組和數組指針

建議61:深入了解數組參數

第7章 結構、位域和枚舉

建議62:結構體的設計要遵循簡單、單一原則

建議62-1:盡量使結構體的功能單一

建議62-2:盡量減小結構體間關系的複雜度

建議62-3:盡量使結構體中元素的個數适中

建議62-4:合理劃分與改進結構體以提高空間效率

建議63:合理利用結構體記憶體對齊原理來提高程式效率

建議64:結構體的長度不一定等于各個成員的長度之和

建議65:避免在結構體之間執行逐位元組比較

建議66:謹慎使用位域

建議67:謹慎使用枚舉

建議68:禁止在位域成員上調用offsetof宏

建議69:深入了解結構體數組和結構體指針

第8章 字元與字元串

建議70:不要忽視字元串的null('0')結尾符

建議70-1:正确認識字元數組和字元串

建議70-2:字元數組必須能夠同時容納字元資料和null結尾符

建議70-3:謹慎字元數組的初始化

建議71:盡量使用const指針來引用字元串常量

建議72:差別strlen函數與sizeof運算符

建議73:在使用不受限制的字元串函數時,必須保證結果字元串不會溢出記憶體

建議73-1:避免字元串拷貝發生溢出

建議73-2:差別串拷貝strcpy與記憶體拷貝memcpy

建議73-3:避免strcpy與memcpy函數記憶體重疊

建議73-4:差別字元串比較與記憶體比較

建議73-5:避免strcat函數發生記憶體重疊與溢出

建議74:謹慎strtok函數的不可重入性

建議75:掌握字元串查找技術

建議75-1:使用strchr與strrchr函數查找單個字元

建議75-2:使用strpbrk函數查找多個字元

建議75-3:使用strstr函數查找一個子串

建議75-4:差別strspn與strcspn函數

第9章 檔案系統

建議76:謹慎使用printf和scanf 函數

建議77:謹慎檔案打開操作

建議77-1:正确指定fopen的mode參數

建議77-2:必須檢查fopen函數的傳回值

建議77-3:盡量避免重複打開已經被打開的檔案

建議77-4:差別fopen與fopen_s函數

建議77-5:差別fopen與freopen函數

建議78:檔案操作完成後必須關閉

建議79:正确了解eof宏

建議80:盡量使用feof和ferror檢測檔案結束和錯誤

建議81:盡量使用fgets替換gets函數

建議82:盡量使用fputs替換puts函數

建議83:合理選擇單個字元讀寫函數

建議84:差別格式化讀寫函數

建議84-1:差別printf/scanf、fprintf/fscanf和sprintf/sscanf

建議84-2:盡量使用snprintf替代sprintf函數

建議84-3:差別vprintf/vscanf、vfprintf/vfscanf 、vsprintf/vsscanf和vsnprintf

建議85:盡量使用fread與fwrite函數來讀寫二進制檔案

建議86:盡量使用fseek替換rewind函數

建議87:盡量使用setvbuf替換setbuf函數

建議88:謹慎remove函數删除已打開的檔案

建議89:謹慎rename函數重命名已經存在的檔案

第10章 預處理器

建議90:謹慎宏定義

建議90-1:在使用宏定義表達式時必須使用完備的括号

建議90-2:盡量消除宏的副作用

建議90-3:避免使用宏建立一種“新語言”

建議91:合理地選擇函數與宏

建議92:盡量使用内聯函數代替宏

建議93:掌握預定義宏

建議94:謹慎使用“#include”

建議94-1:差別“#include ”與“#include "filename.h" ”

建議94-2:必須保證頭檔案名稱的唯一性

建議95:掌握條件編譯指令

建議95-1:使用“#ifndef/#define/#endif”防止頭檔案被重複引用

建議95-2:使用條件編譯指令實作源代碼的部分編譯

建議95-3:妙用“defined”

建議96:盡量避免在一個函數塊中單獨使用“#define”或“#undef”

第11章 斷言與異常處理

建議97:謹慎使用斷言

建議97-1:盡量利用斷言來提高代碼的可測試性

建議97-2:盡量在函數中使用斷言來檢查參數的合法性

建議97-3:避免在斷言表達式中使用改變環境的語句

建議97-4:避免使用斷言去檢查程式錯誤

建議97-5:盡量在防錯性程式設計中使用斷言來進行錯誤報警

建議97-6:用斷言保證沒有定義的特性或功能不被使用

建議97-7:謹慎使用斷言對程式開發環境中的假設進行檢查

建議98:謹慎使用errno

建議98-1:調用errno之前必須先将其清零

建議98-2:避免重定義errno

建議98-3:避免使用errno檢查檔案流錯誤

建議99:謹慎使用函數的傳回值來标志函數是否執行成功

建議100:盡量避免使用goto進行出錯跳轉

建議101:盡量避免使用setjmp與longjmp組合

第12章 記憶體管理

建議102:淺談程式的記憶體結構

建議103:淺談堆和棧

建議104:避免錯誤配置設定記憶體

建議104-1:對記憶體配置設定函數的傳回值必須進行檢查

建議104-2:記憶體資源的配置設定與釋放應該限定在同一子產品或者同一抽象層内進行

建議104-3:必須對記憶體配置設定函數的傳回指針進行強制類型轉換

建議104-4:確定指針指向一塊合法的記憶體

建議104-5:確定為對象配置設定足夠的記憶體空間

建議104-6:禁止執行零長度的記憶體配置設定

建議104-7:避免大型的堆棧配置設定

建議104-8:避免記憶體配置設定成功,但并未初始化

建議105:確定安全釋放記憶體

建議105-1:malloc等記憶體配置設定函數與free必須配對使用

建議105-2:在free之後必須為指針賦一個新值

建議106:避免記憶體越界

建議106-1:避免數組越界

建議106-2:避免sprintf、vsprintf、strcpy、strcat與gets越界

建議106-3:避免memcpy與memset函數長度越界

建議106-4:避免忽略字元串最後的'0'字元而導緻的越界

建議107:避免記憶體洩漏

建議108:避免calloc參數相乘的值超過size_t表示的範圍

第13章 信号處理

建議109:了解信号

建議110:盡量使用sigaction替代signal

建議111:避免在信号處理函數内部通路或修改共享對象

建議112:避免以遞歸方式調用raise函數

第14章 了解c11标準

建議113:謹慎使用_generic

建議114:盡量使用gets_s替換gets函數

建議115:盡量使用帶邊界檢查的字元串操作函數

建議116:了解c11多線程程式設計

建議117:使用靜态斷言_static_assert執行編譯時檢查

建議118:使用_noreturn辨別不傳回值的函數

第15章 保持良好的設計

建議119:避免錯誤地變量初始化

建議120:謹慎使用内聯函數

建議121:避免在函數内定義占用記憶體很大的局部變量

建議122:謹慎設計函數參數的順序和個數

建議123:謹慎使用标準函數庫

建議124:避免不必要的函數調用

建議125:謹慎程式中嵌入彙編代碼

繼續閱讀