Unix C 編程問題精粹
文章目錄 第一章:前言 第二章:約定 第三章:開始任務(wù) 第四章:使用lint 第五章:使用make 第六章:優(yōu)質(zhì)無錯(cuò)編程 第七章:調(diào)試技術(shù) 第八章:其它更好的文檔
第一章:前言
對(duì)于C語言,有人認(rèn)為它已經(jīng)落伍了.對(duì)于這個(gè)問題,仁者見仕,智者見智.的確,C++比C有更強(qiáng)大的諸多優(yōu)勢(shì).但C++是建立在C之上的.這也是Herbert Schildt所著書在全世界暢銷不衰的原因.更何況,要深入學(xué)習(xí)Linux就必需要有相當(dāng)?shù)腃功底.(這也是我搜集整理本文的根由:-) 現(xiàn)結(jié)合個(gè)人在編程中的體會(huì),為使新手少走彎路,為老手錦上添花,因此無論你是使用C或C++編程,也無論你是程序設(shè)計(jì)的初學(xué)者還是成熟的專業(yè)人員,均會(huì)發(fā)現(xiàn),本文將會(huì)對(duì)你有所收益.當(dāng)然,我盡力寫得清晰易懂,又不古板. 我愛C.(正如世人愛上帝一樣:-)..
第二章:約定
專業(yè)的源程書寫風(fēng)格. 先看看世界級(jí)C大師的源程書寫風(fēng)格.如 Steve Maguire 就有許多不錯(cuò)的建議.
[]倡導(dǎo)使用易于理解的'匈牙利式'的命名約定. 所有的字符變量均以ch開始; 如: char ch_****; 所有的字節(jié)變量均冠以b; 如: byte b_****; 所有的長(zhǎng)字變量均冠以l; 如: long l_****; 所有的指針變量均冠以P; 如: char *p_ch_****; 建議類型派生出的基本名字之后加上一個(gè)以大寫字母開頭的'標(biāo)簽'.如: 分析 char **ppchMydata; 其讓人一眼就能看出它代表一個(gè)指向字符指針Mydata的指針. '匈牙利式'命名的最大不足是難念:-(( .但相對(duì)于不是總統(tǒng)演講稿的C源程來說,這又算得了什么?想想看以下的數(shù)據(jù)命名: char a,b,c; long d,e,f; . . . (反正我是不會(huì)再看下去了...)
[]倡導(dǎo)規(guī)范書寫. 如果你思如泉涌,而不去也不及顧慮書寫格式,那也沒關(guān)系.在將其交出去之前,用cb命令格式化你的源程.雖然源程的格式不會(huì)影響到你編譯結(jié)果的正確性,但切記,能讓其他的程序員能輕松地閱讀它.否則沒人會(huì)理你的. 關(guān)于cb命令的更多用法,可以用man cb來參考其手冊(cè)頁(yè). 當(dāng)然除了cb之外,還有更多更好的.但cb是你在任何Unix(LINUX)上都找得到的.更何況它并不差.
第三章:開始任務(wù)
開始任務(wù)之前,先做個(gè)深呼吸!
[]其他文檔你準(zhǔn)備好了嗎? 你是不是除了C源程之外一無所有了嗎?兵馬未動(dòng),糧草先行.你必須先清楚該程序所要完成的功能.在開始寫程序之前,對(duì)程序的功能應(yīng)有規(guī)范說明.書寫規(guī)范書和確知程序功能的一個(gè)方法是先編寫相應(yīng)的操作手冊(cè).如果你是一人單干,勸你首先寫需求書.切記切記,這對(duì)你意味著事半功倍的大好事. 一個(gè)實(shí)例:我計(jì)劃為本行的信貸子功能模塊打一個(gè)補(bǔ)丁.我用10周的時(shí)間用來寫規(guī)劃書,需求書,操作流程,使用說明等等文檔.之后用2周的時(shí)間編寫程序,在初步測(cè)試(1周)后遞交給各信貸部門測(cè)試使用.然后根據(jù)反饋的信息再更改相應(yīng)文檔,并根據(jù)文檔修改源程.6個(gè)月后發(fā)布正式版.
[]一定該遵循ANSI標(biāo)準(zhǔn)嗎? 如果你僅使用ANSI的標(biāo)準(zhǔn)首標(biāo)文件,恭喜你,你的程序有著全世界范圍內(nèi)的廣泛支持和兼容.光明無限.但你必須在通用與專用之間做出取舍,對(duì)不起,我?guī)筒涣四? 我的原則是:核心用ANSI,界面按需而取.這樣在轉(zhuǎn)換平臺(tái)時(shí)僅需另編用戶界面而已.實(shí)用至上嘛. 附:ANSI 標(biāo)準(zhǔn)C頭文件
是不是很寒酸?
[]再續(xù)前緣? 在得到新任務(wù)之后并在開始該新任務(wù)之前應(yīng)馬上回想有哪些是曾經(jīng)擁有的.舊調(diào)重彈遠(yuǎn)比另起爐灶來的高效與環(huán)保.
[]是否該有自已的庫(kù)? 我的答案是應(yīng)該有自已的特色庫(kù),并與ANSI兼容.與3.8不同的是,你僅需在源程序之后附上自已的專用庫(kù)就可以了.其次在有了自已的庫(kù)后,源碼會(huì)很精煉的.不用去羨慕別人了吧.
[]要學(xué)會(huì)條件編譯.注意你的平臺(tái)特性.(高手的標(biāo)志?) 除非你確定你要寫的程序是在某特定的OS特定的硬件平臺(tái)而量身定做.否則應(yīng)注意數(shù)據(jù)類型的長(zhǎng)度,精度都是不同的,不要想當(dāng)然.有時(shí)甚至是不同的編譯器的差異都要考慮考慮.
.... ....(歡迎您來充實(shí)此處空白) ....
好了,在任務(wù)中,又有哪些細(xì)節(jié)呢?
[]我是不是葛郎臺(tái)? 不要那么吝嗇.在源程序中加入詳盡的注釋以使自己和他人即使在許多年以后仍能讀明白它是什么樣的程序. 用注釋行分離各個(gè)函數(shù).
[]刪除不需要的代碼時(shí)要小心. 一個(gè)好建議是:使用#ifdef DEL,而不是簡(jiǎn)單地注釋掉甚至是粗暴地直接dd.如果你是使用/* ... */,但一旦要?jiǎng)h除的代碼有很多行,或注釋中以有注釋時(shí),這就可能不那么好使了.
[]如何給源程序文件命名? 表現(xiàn)特色且不與任何原有應(yīng)用名相同.一個(gè)簡(jiǎn)單地方法就是試試看,系統(tǒng)有什么樣地反應(yīng)?
[]一次只修改一個(gè)地方.
[]一次只編寫一個(gè)單一功能的函數(shù)。
[]編寫通用程序. 只有當(dāng)程序編寫完,并且完成了所需要的性能要求之后,再反過頭來優(yōu)化該程序.
[]不要使用a.out作為結(jié)果.你大可以使用與源程相同的可執(zhí)行文件名.
[]是否一定要用VI編輯? LINUX下有許多專用編程編輯器.它們能使你有更高的效率和更低的低級(jí)輸入錯(cuò)誤,但我還是要?jiǎng)衲阒辽僖炀氄莆誚I.畢竟VI遍地開花.
[]協(xié)同作業(yè).請(qǐng)相信,你不是在孤軍作戰(zhàn).因此,你有必要熟練掌握一些其它的工具.如
.... ....(歡迎您來充實(shí)此處空白) ....
第四章:使用lint
lint沒有你想象中的那樣糟糕.相反,一旦源程序形成了沒有LINT錯(cuò)誤的形式,將很容易保持下去,并享受到如此而帶來的好處.
[]在cc(gcc)之前就應(yīng)使用LINT. lint是一語法檢查程序,對(duì)于這個(gè)多嘴的婆婆來說,你應(yīng)有足夠的耐心.雖然你知道自已在干什么,但在CC之前使用LINT總是一個(gè)好習(xí)慣.
[]lint有哪些特色? 在編譯之前使用lint的重要原因是LINT不但能發(fā)現(xiàn)ANSI C中的語法錯(cuò)誤,而且也能指出潛在的問題或是難于移植于另一機(jī)器的代碼問題.除了能指出簡(jiǎn)單語法錯(cuò)誤之外,LINUT還能基于以下原因指出另外的錯(cuò)誤: A.無法達(dá)到的語句. B.沒有進(jìn)入循環(huán). C.沒有被使用的變量. D.函數(shù)參數(shù)從未使用. E.沒有賦值之前自動(dòng)使用參數(shù). F.函數(shù)在有些地方有返回值,但在其他地方不返回. G.函數(shù)調(diào)用在不同地方使得參數(shù)個(gè)數(shù)不同. H.錯(cuò)誤使用結(jié)構(gòu)指針. I.模糊使用操作符優(yōu)先級(jí). 呵呵呵,挺有用的吧!
[]如何控制LINT的輸出? 有時(shí)LINT會(huì)有一大屏一大屏的警告信息.但似乎并未指出錯(cuò)誤.為了找出潛在的錯(cuò)誤則需費(fèi)心費(fèi)力地瀏覽這些大量的警告信息. 但如果你的程序會(huì)分出幾個(gè)獨(dú)立的模塊,在初級(jí)啟動(dòng)LINT時(shí)不要用可選項(xiàng).當(dāng)對(duì)這些模塊進(jìn)行更改或擴(kuò)充時(shí),可以忽略與代碼無關(guān)的某些警告.為此可用以下選擇項(xiàng): -h 對(duì)判別是否有錯(cuò),類型是否正確不給出啟發(fā)式測(cè)試. -v 不管函數(shù)中沒有定義的參數(shù) -u 不管被使用的變量和函數(shù)沒有定義或定義了但沒有使用.
[]干脆,在程序中插入指令來影響LINT運(yùn)行.它看樣子有些像注釋. /*NOTREACHED*/ 不可達(dá)到的代碼不給信息說明. /*VARARGSn*/ 函數(shù)的變量個(gè)數(shù)不作通常的檢查,只檢查開始n個(gè)參數(shù)的數(shù)據(jù)類型. /*NOSTRUCT*/ 對(duì)下一個(gè)表達(dá)式不作嚴(yán)格類型檢查. /*ARGUSED*/ 下一函數(shù)中,不給出沒被使用參數(shù)的警告信息. /*LINTLIBRARY*/ 置于文件的開頭,它將不給出沒被使用函數(shù)的警告信息.
關(guān)于LINT的更多用法,請(qǐng)用man lint來獲知.
第五章:使用make
[]什么是make?
Unix(Linux)是一個(gè)天生的開發(fā)平臺(tái),我為此感到高興.make是一個(gè)強(qiáng)力的工具.它能自動(dòng)跟蹤相互依賴的源代碼塊并組成一程序,使得很容易建立一可執(zhí)行程序.Make就是這種有依賴關(guān)系的部分和代碼之間所作的規(guī)格說明.
[] 所有的程序都要使用make? 是的.盡管你只有幾個(gè)簡(jiǎn)單的模塊,但你需要有一種結(jié)構(gòu)來支持它從簡(jiǎn)單走向復(fù)雜.除非你的程序已經(jīng)蓋棺定論.
[]Makefile由哪些組成? Makefile由以下幾個(gè)部分組成:
注釋. ^^^^ 使用#符號(hào)插入.make將忽略#之后的任何內(nèi)容以及其后的RETURN鍵.
變量. ^^^^ make允許定義與SHELL變量類似的有名變量.比如,你定義了SOURCES=prog.c,那么該變量的值$(SCOURES)就包含了源文件名.
依賴關(guān)系. ^^^^^^^^ 左邊是目標(biāo)模塊,后接一冒號(hào).再接與該模塊有依賴關(guān)系的模塊.
命令. ^^^^ 以TAB鍵開始(即使用相同數(shù)量的空格也不能代替它).
[]Makefile示例 下面介紹一個(gè)簡(jiǎn)單的示例來說明make的用法.假設(shè)你的程序有兩個(gè)源文件main.c和myc.c,一個(gè)位於子目錄include下的頭文件myhead.h,一個(gè)庫(kù)由三個(gè)源文件myrout1.c,myrout2.c,myrout3.c產(chǎn)生. 其makefile文件為: #一個(gè)基本的MAKEFILE文件. #其中包括個(gè)人的頭文件和個(gè)人庫(kù). HEADERS=include/myhead.h SOURCES=main.c myc.c PRODUCT=$(HOME)/bin/tool LIB=myrout.a LIBSOURES=myrout1.c myrout2.c myrout3.c CC=cc CFLAGS=-g all:$(PRODUCT) $(PRODUCT):$(SOURCES) $(CC)$(CFLAGS) -o $(PRODUCT)$(SOURCES) lint:$(PRODUCT) lint $(SOURCES)$(LIBSOURCES) 哈哈,挺象SHELL編程的.如果你與我一樣使用LINUX下的gcc,那么只要把上面的CC=cc改為CC=gcc即可.怎么樣,想來一個(gè)更復(fù)雜點(diǎn)的嗎?
[]一個(gè)更為復(fù)雜的Makefile 你是否注意到,在上例中,只要啟動(dòng)make,就會(huì)重新編譯所有源代碼. 如果你能看懂以下的makefile,恭喜恭喜,你通關(guān)了. #一個(gè)更為復(fù)雜的makefile HEADERS=include/myhead.h SOURES=main.c myc.c OBJECTS=main.c myc.c PRODUCT=$(HOME)/bin/tool LIB=myrout.a LIBSOURCES=myrout1.c myrout2.c myrout3.c LIBOBJECTS=$(LIB)(myrout1.o)$(LIB)(myrout2.o)$(LIB)(myrout3.o) INCLUDE=include CC=cc CFLAGS=-g -Xc LINT=lint LINTFLAGS=-Xc all:$(PRODUCT) $(PRODUCT):$(OBJECTS)$(LIB) $(CC)(CFLAGS)-o$(PRODUCT)$(OBJECTS)$(LIB) .c.o: $(HEADERS) $(CC)$(CFLAGS) -c I$(INCLUDE)$< $(LIB):$(HEADERS)$(LIBSOURCES) $(CC) $(CFLAGS) -c $(?:.o=.c) ar rv $(LIB) $? rm $? .c.c:; lint: $(PRODUCT) $(LINT)$(LINIFLAGS)$(SOURCES)$LIBSOURCES)
第六章:優(yōu)質(zhì)無錯(cuò)編程
親愛的,檢查一下,你是否注意到了以下的細(xì)節(jié)?也就是說,你是否是一個(gè)合格的,能編寫優(yōu)質(zhì)無錯(cuò)代碼的程序員?要永遠(yuǎn)記住,編寫無錯(cuò)代碼是程序員的責(zé)任,而不是測(cè)試員.(摘錄于本人的'細(xì)節(jié)頁(yè)',因此本節(jié)將永遠(yuǎn)不會(huì)保持完整,歡迎您來充實(shí)她)
[]所有程序員至少出現(xiàn)過的一個(gè)錯(cuò)誤: if(a=3){......}如果a等于3,那么...... 你至少要養(yǎng)成這樣的習(xí)慣:當(dāng)判斷一個(gè)變量與一個(gè)常量是否相等時(shí),將常量寫在前面.這樣即使你一不小心寫成這樣:if(3=a){......}在cc 之前就可以很容易發(fā)現(xiàn)它.
[]老調(diào)重彈:邏輯操作符的優(yōu)先權(quán). 我不愿多嘴.總之,如果你一定要編寫如下代碼時(shí): if(a&0x1&&b&0x2){......} 你的手頭最好有一本詳盡的指南.或者你是這方面的專家.
[]盡量不使用int數(shù)據(jù)類型. 這僅是一個(gè)忠告.你大可使用char,short,long數(shù)據(jù)類型.若干年以后,當(dāng)你成長(zhǎng)為高手之時(shí),你會(huì)發(fā)現(xiàn)此時(shí)我的良苦用心.
[]對(duì)于非整型函數(shù)一定要完整定義. 如 long float jisuan(char chArr[],int chNum) { long float lMydata; ... ... return(lMydata); }
[]對(duì)于非整型函數(shù)的輸入要當(dāng)心. 如 long float lfNum; ... ... scanf('%lf',&lfNum);
[]float 型的有效數(shù)字為7位.當(dāng)多于7位時(shí),第8位及以后的位將不準(zhǔn)確,可以將其定義為long float型.
[]文件的輸入出盡量采用fread fwrite函數(shù).只有當(dāng)另有用途時(shí)才用fprintf fscanf 函數(shù).
[]對(duì)于數(shù)組及字符串的比較操作時(shí)要確認(rèn)以''結(jié)束.
第七章:調(diào)試技術(shù)
調(diào)試技術(shù)在本文中不太好說,之所以將其獨(dú)立成章是想套用M$的老話:'在下一版本中將會(huì)做得更好':-((.其實(shí)這類文章在全國(guó)各大BBS上滿天飛.
在此我只想說說程序員的應(yīng)盡職責(zé)之一:在程序中使用斷言. ~~~~ []既要維護(hù)程序的交付版本,又要維護(hù)程序的調(diào)試版本,這時(shí)可以利用斷言補(bǔ)救.
[]要使用斷言對(duì)函數(shù)參數(shù)進(jìn)行確認(rèn).
[]要從程序中刪除無定義的特性,或者在程序中使用斷言來檢查出無定義特性的非法使用.
[]不要浪費(fèi)別人的時(shí)間,詳細(xì)說明不清楚的斷言.
[]消除所做的隱式假定,或者利用斷言檢查其正確性.
[]利用斷言來檢查不可能發(fā)生的情況.
一個(gè)實(shí)例:我在我的源程序中都使用斷言.在本人所編制的全國(guó)電子匯兌模糊檢索功能模塊測(cè)試中,前臺(tái)人員氣喘吁吁地告訴我,屏幕上出現(xiàn)了不認(rèn)識(shí)的英文.我說最前面的是哪幾個(gè)數(shù)字,然后根據(jù)此數(shù)字查閱斷言文檔,原來是前日日終未正常結(jié)束,經(jīng)查只有半個(gè)庫(kù).這種情況極少發(fā)生,但不是不可能發(fā)生.使用斷言能及時(shí)正確地判別是否是程序的錯(cuò)誤還是外部的因素.因此使用斷言,是將錯(cuò)誤消滅在發(fā)生錯(cuò)誤之前的一個(gè)極其重要的手法.這也是判斷一個(gè)程序員是否具備良好素質(zhì)的一個(gè)方面.
