2016年,C语言该怎样写
C語言的首要原則是——能不寫C語言就不寫。如果一定要寫,請遵守現(xiàn)代規(guī)則。
C語言誕生于20世紀(jì)70年代初。人們在其發(fā)展的各個階段都在“學(xué)習(xí)C語言”,但在學(xué)習(xí)C語言之后,知識往往停滯不前,從開始學(xué)習(xí)它的那年起積攢起不同觀點。
很重要的一點是,用C語言開發(fā),不要再繼續(xù)認(rèn)為“那是我在八零或者九零年代學(xué)習(xí)的東西”。
本文首先假定處于符合現(xiàn)代設(shè)計標(biāo)準(zhǔn)的平臺上,并且不存在需要兼容過多遺留約束。我們當(dāng)然不該僅因為某些公司拒絕更新其二十年前的老系統(tǒng)就完全遵守陳舊的標(biāo)準(zhǔn)。
準(zhǔn)備
C99標(biāo)準(zhǔn)(C99和C11分別是指1999年和2011年誕生的C語言標(biāo)準(zhǔn),顯然C11標(biāo)準(zhǔn)比較新)。
clang(一種C語言編譯器),默認(rèn)情況
- clang使用C11的一種擴(kuò)展版本(GNU C11模式),所以不需要多余的配置就可以適應(yīng)現(xiàn)代特性。
- 如果你想使用標(biāo)準(zhǔn)的C11,需要指定-std=c11;如果你想使用標(biāo)準(zhǔn)C99,則指定-std=c99。
- 與gcc相比,clang編譯文件速度更快。
gcc需要指定-std=c99或-std=c11
- gcc構(gòu)建源文件的速度一般慢于clang,但某些時候卻能保證編碼速度更快。性能比較和回歸測試也非常重要。
- gcc-5默認(rèn)使GNU C11模型(與clang相同),但如果你確切地需要C11或C99,仍應(yīng)指定-std=c11或-std=c99。
優(yōu)化
-O2,-O3
- 通常想使用-O2,但有時也使用-O3。在兩個級別下(通過編譯器)分別進(jìn)行測試并且保持最佳性能。
-Os
- -Os如果你關(guān)注緩存效率(本該如此),這個選項能幫上你。
警告
-wall -Wextra -pedantic
- 最新版本的編譯器支持-Wpedantic,但為了向后兼容其也接受古老的-pedantic。
在測試過程中,應(yīng)該在所有的平臺上都添加-Werror和-Wshadow。
- 因為不同的平臺、編譯器和庫會發(fā)出不同警告,通過使用-Werror可更有針對性地部署生產(chǎn)資源。你或許并不想僅因為某個平臺上從未見過的GCC版本在新的運行方式下時報錯就毀掉用戶的全部構(gòu)建。
額外選擇包括-Wstrict-overflow -fno-strict-aliasing。
- 要么指定-fno-strict-aliasing,要么就確保只以對象創(chuàng)建時的類型對其進(jìn)行訪問。因為許多C語言代碼擁有跨類型的別名,當(dāng)不能控制整個底層源代碼樹時,使用-fno-strict-aliasing是個不錯的選擇。
到目前為止,clang有時會對一些有效語法發(fā)出警告,所以需要添加-Wno-
- missing-field-initializers。GCC在4.7.0版本后修正了這些不必要的警告。
構(gòu)建
編譯單元
- 構(gòu)建C程序項目的最常見方式是將每個C源文件分解成目標(biāo)文件,最后將所有目標(biāo)文件鏈接到一起。這個過程在增量開發(fā)中表現(xiàn)很好,但從性能和優(yōu)化角度看,并非最優(yōu)。因為在這種方式下編譯器不能檢測出跨文件的潛在優(yōu)化之處。
LTO——鏈接時優(yōu)化
- LTO通過使用中間表示方式對目標(biāo)文件進(jìn)行處理,因此解決了“跨編譯單元源文件分析和優(yōu)化問題”,所以source-aware優(yōu)化可在鏈接時跨編譯單元實現(xiàn)。
- LTO明顯減緩了鏈接過程,但make-j會有所幫助。
- clang LTO (http://llvm.org/docs/LinkTimeOptimization.html)
- gcc LTO (https://gcc.gnu.org/onlinedocs/gccint/LTO-Overview.html)
- 到2016年為止,clang和gcc都可通過在目標(biāo)文件編譯和最后庫/程序鏈接時,向命令行選項中添加-flto來支持LTO。
- 不過LTO仍需監(jiān)管。有時,程序中一些代碼沒有被直接使用,而是被附加的庫使用了。因為LTO會在全局性鏈接時檢測出沒有使用或者不可訪問的代碼,也可能將其刪除,所以這些代碼將不會包含到最后的鏈接結(jié)果中。
架構(gòu)
-march = native
- 允許編譯器使用CPU完整的特性集。
- 再一次,性能測試和回歸測試都非常重要(之后在多個編譯器以及多個版本中對結(jié)果進(jìn)行比較)以確保任何啟用的優(yōu)化都不會產(chǎn)生副作用。
如果你使用not-your-build-machine特性,-msse2和-msse4.2可能起到作用。
編寫代碼
類型
如果你將char、int、short、long或unsigned類型寫入新代碼中,就把錯了。
對于現(xiàn)代程序,應(yīng)該先輸入#include,之后再使用標(biāo)準(zhǔn)類型。
更多細(xì)節(jié),參見“stdint.h規(guī)范”(http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdint.h.html)。
常見的標(biāo)準(zhǔn)類型有:
- int8_t,int16_t,int32_t,int64_t——符號數(shù)
- uint8_t,uint16_t,uint32_t,uint64_t——無符號數(shù)
- float——標(biāo)準(zhǔn)32位浮點數(shù)
- double——標(biāo)準(zhǔn)64位浮點數(shù)
到底用不用int
一些讀者說他們真的非常喜愛int類型,但我們不得不把它從他們僵硬的手指中撬出來。在技術(shù)層面我想指出的是,如果在你的控制下類型的位數(shù)發(fā)生了改變,這時還希望程序正確運行是不可能的。
當(dāng)然你也可以找出inttypes.h文件中的RATIONALE(http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/inttypes.h.html#tag_13_19_06)來看看為何使用長度不固定的類型是不安全的。
如果你夠聰明到可以在平臺上對整個開發(fā)過程中的int類型統(tǒng)一定為16或32位,并且也在每個使用了它的地方測試所有16位和32位邊界情況,那就可以隨意使用int類型了。
對于我們中不能在編程時將多層決策樹規(guī)范層次清晰地記住的人,可以使用固定位數(shù)的類型,這樣在編程時就可以不必憂心那些麻煩的概念和測試開銷。
或者,規(guī)范中更加簡潔地指出:“ISO C語言標(biāo)準(zhǔn)中整數(shù)擴(kuò)展規(guī)則將會造成意外改變。”
所以當(dāng)使用int時,祝你好運!
使用char的例外情況
到2016年,char類型唯一的用處就是舊API中需要(比如strncat,printf中)?;蛘叱跏蓟粋€只讀字符串(比如const char * hello = “hello”;),這是因為C語言字符串類型字面值(”hello”)是char []。
同樣,在C11中,我們也有原生的unicode支持,并且UTF-8的字符串類型仍然是char [],甚至是像const char * abcgrr =u8”abc “; 這樣的多字節(jié)序列也一樣。
使用int、long等的例外情況
如果你正在使用帶有原生返回類型或原生參數(shù)的函數(shù),那么請使用函數(shù)原型或者API規(guī)范中描述的類型。
符號
無論何時都不應(yīng)該將unsigned這個詞寫進(jìn)代碼。丑陋的C語言規(guī)定往往會因多字類型而損壞了程序可讀性和使用性,而現(xiàn)在我們編寫的代碼不再需要遵守那些規(guī)定了。當(dāng)能夠輸入uint64_t時又有誰想用unsigned long long int呢?中的類型更加明確,含義更加確切,也能夠更好地表達(dá)意義,并且具有更好的排版,提高了程序的使用性和可讀性。
指針作為整型
但你可能會說“當(dāng)遇到麻煩的指針數(shù)學(xué)時,我不得不將指針轉(zhuǎn)換為long!”。這是錯誤的。
正確的指針數(shù)學(xué)類型是使用中定義的unitptr_t,當(dāng)然同樣非常有用的ptrdiff_t也定義在stddef.h中。
而并非:
long diff = (long)ptrOld - (long)ptrNew;你可以使用:
ptrdiff_t diff = (uintptr_t)ptrOld - (uintptr_t)ptrNew;也可以用:
printf("%p is unaligned by %" PRIuPTR " bytes.\n", (void *)p, ((uintptr_t)somePtr & (sizeof(void *) - 1)));系統(tǒng)依賴的類型
你繼續(xù)爭辯:“在32位平臺上我想要32位字長,而在64位平臺上我要64位字長?!?/p>
這時思路將故意引入使用平臺依賴的兩種不同字長,會對編程造成困難。但是如果跳過這個問題,你仍然不想使用依賴系統(tǒng)的類型。
在此情況下,該使用intptr_t——當(dāng)前平臺定義為字長的整型。在32位平臺上,intptr_t = int32_t;64位平臺上,intptr_t = int64_t。intptr_t也有uintptr_t的形式。
對管理指針的偏移來說,我們還有ptrdiff_t,該類型適于存儲指針減少的數(shù)值。
最大值容器
你需要一種能承載系統(tǒng)中所有可能的整數(shù)的類型嗎?在這種情況下,人們傾向于使用已知的最大類型,比如將較小的無符號類型轉(zhuǎn)換成uint64_t。從實際技術(shù)上講,有一種更正確的方式來保證可以裝載任何值。
對于所有整數(shù)來說,最安全的容器是intmax_t(或者uintmax_t)。可以無精度損失地將任何符號整數(shù)(或無符號整數(shù))賦值或者轉(zhuǎn)換為這種形式。
其他類型
被廣泛使用的系統(tǒng)依賴類型是stdded.h中提供的size_t。它在程序中非常基礎(chǔ),因為它是“能夠容納最大的數(shù)組索引的整數(shù)”,這也意味著它能容納最大的內(nèi)存偏移。在實際應(yīng)用中,size_t是運算符sizeof的返回類型。
size_t實際上與uintptr_t一樣都是定義在所有現(xiàn)代平臺上的。所以在32位的平臺上,size_t = uint32_t,而在64位平臺上,size_t = uint64_t。
還有一個帶有符號的size_t,也就是ssize_t,它用來表示在庫函數(shù)出現(xiàn)錯誤時返回-1(注意,sszie_t是POSIX,不適用于Windows接口)。
所以,難道你不該在自己的函數(shù)參數(shù)中使用size_t來適應(yīng)任何字長的系統(tǒng)么?從技術(shù)上講,它是sizeof的返回值,所以任何接受多字節(jié)數(shù)值的函數(shù)都允許表示成size_t類型。
其他用途包括:size_t類型可作為malloc的參數(shù),并且是read()和write()的返回類型(除了Windows平臺上ssize_t不存在,其返回值是int類型)。
輸出類型
你不應(yīng)在輸出時進(jìn)行類型轉(zhuǎn)換,而是永遠(yuǎn)使用inttypes.h中定義的合適的類型說明符。
包括但不限于以下情況:
- size_t——%zu
- ssize_t——%zd
- ptrdiff_t——%td
- 原始指針值%p(現(xiàn)代編譯器中輸出16進(jìn)制數(shù); 0 首先將指針轉(zhuǎn)換為(void *))
- int64_t——”%” PRId64
- uint64_t——”%” PRIu64(64位類型應(yīng)該只能使0 用PRI[udixXo]64風(fēng)格的宏)
- intptr_t——”%” PRIdPTR
- uintptr_t——”%” PRIuPTR
- intmax_t——”%” PRIdMAX
- uintmax_t——”%” PRIuMAX
關(guān)于PRI* 格式化說明符需要注意:它們都是宏和宏在特定的平臺基礎(chǔ)上為了合適的輸出而做的擴(kuò)展。這意味著你不能這樣:
printf("Local number: %PRIdPTR\n\n", someIntPtr);并且因為它們是宏,應(yīng)該:
printf("Local number: %" PRIdPTR "\n\n", someIntPtr);注意應(yīng)將%放進(jìn)格式化字符串內(nèi),但類型指示符在格式化字符串外部,這是因為所有相鄰的字符串都被預(yù)處理器連接成為最后的結(jié)合字符串。
C99允許在任何地方進(jìn)行變量聲明
所以,千萬不要這樣做:
void test(uint8_t input) {uint32_t b;if (input > 3) {return;}b = input; }而是這樣:
void test(uint8_t input) {if (input > 3) {return;}uint32_t b = input; }警告:如果存在緊密的循環(huán),請測試初始值的配置。有時分散的聲明會造成意外減速。對于沒有選擇最快路徑的代碼(事情往往這樣),最好還是盡可能清晰。而且,與初始化一起定義類型會極大提升可讀性。
C99允許在 for 循環(huán)中聲明內(nèi)聯(lián)計數(shù)器
所以不要這樣:
uint32_t i; for (i = 0; i < 10; i++)而應(yīng)該:
for (uint32_t i = 0; i < 10; i++)一個例外:如果你需要在循環(huán)結(jié)束后仍保持計數(shù)器的值,那當(dāng)然不要在循環(huán)域內(nèi)聲明計數(shù)器。
現(xiàn)代編譯器支持 #pragma once
所以不要:
#ifndef PROJECT_HEADERNAME #define PROJECT_HEADERNAME . . . #endif /* PROJECT_HEADERNAME */而應(yīng)該:
#pragma once#pragma once告訴編譯器只包含頭文件一次,且不再需要那三行頭部約束了。所有編譯器和平臺都支持并且推薦這樣的編譯方式,而不建議使用手動編寫頭部約束。更多細(xì)節(jié),參見pragma once中編譯器支持的列表(https://en.wikipedia.org/wiki/Pragma_once)。
C語言允許對自動分配的數(shù)組進(jìn)行靜態(tài)初始化
所以,不要:
uint32_t numbers[64]; memset(numbers, 0, sizeof(numbers));而應(yīng)該:
uint32_t numbers[64] = {0};C語言允許對自動分配的結(jié)構(gòu)體進(jìn)行靜態(tài)初始化
不要:
struct thing {uint64_t index;uint32_t counter; }; struct thing localThing; void initThing(void) {memset(&localThing, 0, sizeof(localThing)); }而應(yīng)該:
struct thing {uint64_t index;uint32_t counter; }; struct thing localThing = {0};重要提示:如果結(jié)構(gòu)體存在填充,{0}方法不會將額外的字節(jié)歸零。例如結(jié)構(gòu)體struct thing在counter(64位平臺上)后需要填充4字節(jié),并且結(jié)構(gòu)體以字長度進(jìn)行增量填充。如果需要將包含沒有使用的填充在內(nèi)的整個結(jié)構(gòu)體置0,即使可尋址內(nèi)容只有8+4=12字節(jié),也需要使用memset(&localThing, 0, sizeof(localThing)) ,并且其中sizeof(localThing) == 16 字節(jié)。
如果需要重新初始化已存在并且完成了分配的結(jié)構(gòu)體,那么就為后面的任務(wù)聲明一個全0的結(jié)構(gòu)體:
struct thing {uint64_t index;uint32_t counter; }; static const struct thing localThingNull = {0}; ... struct thing localThing = {.counter = 3}; ... localThing = localThingNull;如果你足夠幸運是在C99(或者更新)的開發(fā)環(huán)境中,就可以使用復(fù)合文字而不是全0結(jié)構(gòu)體,參見The New C: Compound Literals(http://www.drdobbs.com/the-new-c-compound-literals/184401404)。復(fù)合文字允許編譯器自動創(chuàng)建臨時匿名結(jié)構(gòu)體,并將其值拷貝到目標(biāo)中:
localThing = (struct thing){0};C99添加可變長數(shù)組(C11可選)
因此不要這樣:
uintmax_t arrayLength = strtoumax(argv[1], NULL, 10); void *array[]; array = malloc(sizeof(*array) * arrayLength); /* remember to free(array) when you're done using it */而該:
uintmax_t arrayLength = strtoumax(argv[1], NULL, 10); void *array[arrayLength]; /* no need to free array */重要警告:通常在棧區(qū)分配空間時,變長數(shù)組和普通數(shù)組一樣。如果不想靜態(tài)地創(chuàng)建一個含300萬個元素的普通數(shù)組,那就不要試圖在運行時使用這種語法來創(chuàng)建如此巨大的數(shù)組。這可不是靈活的Python、Ruby中自動增長的列表。如果運行時指定了一個數(shù)組的長度,而這個長度對于棧區(qū)而言過大的話,程序?qū)霈F(xiàn)可怕的事情(崩潰、安全問題)。對于簡單的,用途單一的情況,變長數(shù)組非常方便,但它不能支撐大規(guī)模軟件開發(fā)。如果你需要3個元素的數(shù)組,而有時又需要300萬個,那么千萬不要使用變長數(shù)組。
了解變長數(shù)組的語法的確不錯,因為沒準(zhǔn)你就會遇到(或者想來一次快速測試)。但有可能因為忘記邊界檢查,或忘記了目標(biāo)平臺上沒有足夠的棧空間而使程序徹底崩潰,這是非常危險的反模式語法。
注意:必須確定數(shù)長度是合理的值。(比如小于幾KB,有時平臺上的棧最大空間為4KB)。你不能在棧中分配巨大的數(shù)組(數(shù)百萬條項目),但如果數(shù)組長度是有限的,使用C99的變長數(shù)組功能比通過malloc手動請求堆內(nèi)存要簡單得多。
再次注意:如果沒有對用戶的輸入進(jìn)行檢查,那么用戶就能夠通過分配一個巨大的變長數(shù)組輕松地殺死程序。一些人指出變長數(shù)組與模式相抵觸,但是如果嚴(yán)格檢查邊界,在某些情況下,或許會表現(xiàn)得更好。
C99允許注釋非重疊的指針參數(shù)
參看限制關(guān)鍵字 (https://en.wikipedia.org/wiki/Restrict,通常是 _restrict)
參數(shù)類型
如果函數(shù)能接受任意輸入數(shù)據(jù)和任意長度的程序,那就不要限制參數(shù)的類型。
所以,不要這樣:
void processAddBytesOverflow(uint8_t *bytes, uint32_t len) {for (uint32_t i = 0; i < len; i++) {bytes[0] += bytes[i];} }而應(yīng)該:
void processAddBytesOverflow(void *input, uint32_t len) {uint8_t *bytes = input;for (uint32_t i = 0; i < len; i++) {bytes[0] += bytes[i];} }函數(shù)的輸入類型只是描述了代碼的接口,而不是帶有參數(shù)的代碼在做什么。上述代碼接口意思是“接受一個字節(jié)數(shù)組和一個長度”,所以不并想限定函數(shù)調(diào)用者只能使用uint_8字節(jié)流。也許用戶甚至想將一個老式的char*值或者其他什么意想不到的東西傳遞給你的函數(shù)。
通過聲明輸入類型為void*然后重新指定或轉(zhuǎn)換成為實際想在函數(shù)中使用的類型,這樣就將用戶從費力思考你自己的函數(shù)庫文件的內(nèi)部抽象中拯救出來。
一些讀者指出這個例子中的對齊問題,但我們訪問的是輸入的單字節(jié)元素,所以一切都沒問題。如果不是這樣的話,我們就是在將輸入轉(zhuǎn)換為更大的類型,這時候就需要注意對齊問題了。對于處理跨平臺對齊的問題的不同寫操作,參看Unaligned Memory Access部分章節(jié)(https://www.kernel.org/doc/Documentation/unaligned-memory-access.txt,記住:這頁概述細(xì)節(jié)不是關(guān)于跨體系結(jié)構(gòu)的C,所以擴(kuò)展的知識和經(jīng)驗完全適用于任何示例。)
返回參數(shù)類型
C99給我們了將true定義為1,false定義為0的的權(quán)利。對于成功或失敗的返回值,函數(shù)應(yīng)該返回true或false,而不是手動指定1或0的int32_t返回類型(或者更糟糕1和-1;又或者0代表成功,1代表失敗?或0帶代表成功,-1代表失敗)。
如果函數(shù)將輸入?yún)?shù)類型的范圍變得更大的操作無效,那么在任何參數(shù)可能無效的地方,所有API都應(yīng)強(qiáng)制使用雙指針作為參數(shù),而不應(yīng)返回改變的指針。對于大規(guī)模應(yīng)用,那樣“對于某些調(diào)用,返回值使得輸入無效”的程序太容易出錯。
所以,不要這樣:
void *growthOptional(void *grow, size_t currentLen, size_t newLen) {if (newLen > currentLen) {void *newGrow = realloc(grow, newLen);if (newGrow) {/* resize success */grow = newGrow;} else {/* resize failed, free existing and signal failure through NULL */free(grow);grow = NULL;}}return grow; }而應(yīng)該這樣:
/* Return value:* - 'true' if newLen > currentLen and attempted to grow* - 'true' does not signify success here, the success is still in '*_grow'* - 'false' if newLen <= currentLen */ bool growthOptional(void **_grow, size_t currentLen, size_t newLen) {void *grow = *_grow;if (newLen > currentLen) {void *newGrow = realloc(grow, newLen);if (newGrow) {/* resize success */*_grow = newGrow;return true;}/* resize failure */free(grow);*_grow = NULL;/* for this function,* 'true' doesn't mean success, it means 'attempted grow' */return true;}return false; }如果這樣的話就更好了:
typedef enum growthResult {GROWTH_RESULT_SUCCESS = 1,GROWTH_RESULT_FAILURE_GROW_NOT_NECESSARY,GROWTH_RESULT_FAILURE_ALLOCATION_FAILED } growthResult;growthResult growthOptional(void **_grow, size_t currentLen, size_t newLen) {void *grow = *_grow;if (newLen > currentLen) {void *newGrow = realloc(grow, newLen);if (newGrow) {/* resize success */*_grow = newGrow;return GROWTH_RESULT_SUCCESS;}/* resize failure, don't remove data because we can signal error */return GROWTH_RESULT_FAILURE_ALLOCATION_FAILED;}return GROWTH_RESULT_FAILURE_GROW_NOT_NECESSARY; }格式化
編碼風(fēng)格非常重要,但同時也沒任何價值。如果項目有50頁編碼風(fēng)格指南,沒人會幫你。但如果你的代碼沒有可讀性,也沒人會幫你。解決方案就是永遠(yuǎn)使用自動的編碼格式化。
2016年,唯一可用的C格式化程序是clang-format。它擁有最好的默認(rèn)C語言格式并且仍在持續(xù)改進(jìn)。
這是我運行clang-format的腳本:
#!/usr/bin/env bash clang-format -style="{BasedOnStyle:llvm,IndentWidth:4,AllowShortFunctionsOnASingleLine: None, KeepEmptyLinesAtTheStartOfBlocks: false}" "$@"然后調(diào)用(假設(shè)你稱之為cleanup-format):
matt@foo:?/repos/badcode% cleanup-format -i *.{c,h,cc,cpp,hpp,cxx}選項-i通過覆蓋已有的文件進(jìn)行格式化,而不是寫入新文件或者創(chuàng)建備份文件。如果有很多文件,可以采取并行方式遞歸處理整個源文件樹。
#!/usr/bin/env bash# note: clang-tidy only accepts one file at a time, but we can run it # parallel against disjoint collections at once. find . \( -name \*.c -or -name \*.cpp -or -name \*.cc \) |xargs -n1 -P4 cleanup-tidy# clang-format accepts multiple files during one run, but let's limit it to 12 # here so we (hopefully) avoid excessive memory usage. find . \( -name \*.c -or -name \*.cpp -or -name \*.cc -or -name \*.h \) |xargs -n12 -P4 cleanup-format -i如今新cleanup-tidy腳本出現(xiàn)了,其內(nèi)容是:
#!/usr/bin/env bash clang-tidy \-fix \-fix-errors \-header-filter=.* \--checks=readability-braces-around-statements,misc-macro-parentheses \$1 \ -- -I.- readability-braces-around-statements——要求所有if/while/for的申明語句全部包含在大括號內(nèi)。C語言允許在循環(huán)和條件語句后面的單條語句的“大括號可選”存在歷史原因。但在編寫現(xiàn)代代碼時不使用大括號來包含循環(huán)和條件將是不可原諒的。也許你會說“但是編譯器承認(rèn)它”并且這樣不會影響代碼的可讀性、可維護(hù)性、可理解性和適用性。但你不是為了取悅編譯器而編程,編程是為了取悅未來數(shù)年后不能了解你當(dāng)時思維的人。
- misc-macro-parentheses——自動添加宏中使用的所有參數(shù)的父類。
clang-tidy運行時非常棒,但卻會因為某些復(fù)雜的代碼卡住。clang-tidy不進(jìn)行格式化,所以需要在排列整齊大括號和宏之后再運行clang-format。
其他想法
永遠(yuǎn)不使用malloc,而應(yīng)該用calloc,當(dāng)?shù)玫綖?的內(nèi)存時將不會有性能損失。如果你不喜歡函數(shù)原型calloc(object count, size per object),可以用#define mycalloc(N) calloc(1, N) 將其封裝起來。
能不用memset就不用
當(dāng)能靜態(tài)初始化結(jié)構(gòu)體或數(shù)組為0時,不要用memset(ptr, 0, len)。不過,如果要對包括填充字節(jié)在內(nèi)的整個結(jié)構(gòu)體進(jìn)行置0時,memset()是你唯一的選擇。
寫在最后
對于編寫大規(guī)模代碼來說,想要不出現(xiàn)任何錯誤是不可能的。就算是我們不必?fù)?dān)心那些像RAM中比特位隨機(jī)翻轉(zhuǎn)的問題或者設(shè)備以未知的幾率出現(xiàn)錯誤的可能性,也會有多種多樣的操作系統(tǒng)、運行狀態(tài)、庫文件以及硬件平臺的問題需要擔(dān)心。
所以我們能做的最好的事情就是——編寫簡單又易于理解的代碼。
原作者在文章結(jié)尾列出了對本文提出意見和建議的讀者,以及深入了解這些技術(shù)細(xì)節(jié)的在線文檔,受篇幅所限,譯文不再贅述。
http://geek.csdn.net/news/detail/63135
文/Matt Stancliff 譯/賈子甲?
感謝Matt Stancliff授權(quán)《程序員》翻譯,原文鏈接https://matt.sh/howto-c。?
本文未經(jīng)允許不得轉(zhuǎn)載,訂閱2016年程序員請點擊?http://dingyue.programmer.com.cn。
總結(jié)
以上是生活随笔為你收集整理的2016年,C语言该怎样写的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Google谷歌首席科学家:神经网络的奇
- 下一篇: 颜色矩