C 語(yǔ)言對(duì)模塊化支持的欠缺
模塊化是最高原則之一(在 《Unix 編程藝術(shù)》一書(shū)中, Unix 哲學(xué)第一條即:模塊原則),我們就當(dāng)考慮如何簡(jiǎn)潔明快的使用 C 語(yǔ)言實(shí)現(xiàn)模塊化。
除開(kāi) C/C++ ,在其它現(xiàn)在流行的開(kāi)發(fā)語(yǔ)言中,缺少標(biāo)準(zhǔn)化的模塊管理機(jī)制是很難想象的。但這也是 C 語(yǔ)言本身的設(shè)計(jì)哲學(xué)決定的:把盡可能多的可能性留給程序員。根據(jù)實(shí)際的系統(tǒng),實(shí)際的需要去定制自己需要的東西。
對(duì)于巨型的系統(tǒng)(比如 Windows 這樣的操作系統(tǒng)),一般會(huì)考慮使用一種二進(jìn)制級(jí)的模塊化方案。由模塊自己提供元信息,或是使用統(tǒng)一的管理方案(比如注冊(cè)表)。稍小一點(diǎn)的系統(tǒng)(我們通常開(kāi)發(fā)接觸到的),則會(huì)考慮輕量一些的源碼級(jí)方案。
首先要考慮的往往是模塊的依賴(lài)關(guān)系和初始化過(guò)程。
依賴(lài)關(guān)系可以放由鏈接器或加載器來(lái)解決。尤其在使用 C 語(yǔ)言時(shí),簡(jiǎn)單的靜態(tài)庫(kù)或動(dòng)態(tài)庫(kù),都不太會(huì)引起大的麻煩。
C++ 則不然,C++ 的某些特性(比如模板類(lèi)靜態(tài)成員的構(gòu)造)必須對(duì)早期只供 C 語(yǔ)言使用的鏈接器做一些增強(qiáng)。即使是精心編寫(xiě)的 C++ 庫(kù),也有可能出現(xiàn)一些意外的 bug 。這些 bug 往往需要對(duì)編譯,鏈接,加載過(guò)程很深刻的理解,才能查出來(lái)。注:我并不想以此來(lái)反對(duì)使用 C++ 做開(kāi)發(fā)。
我們需要著重管理的,是模塊的初始化過(guò)程。
對(duì)于打包在一起的一個(gè)庫(kù)(例如 glibc ,或是 msvcrt ),會(huì)在加載時(shí)有初始化入口,以及卸載時(shí)有結(jié)束代碼。我想說(shuō)的不是這個(gè),而是我們自己內(nèi)部拆分的更小的模塊的相互依賴(lài)關(guān)系。
誰(shuí)先初始化,誰(shuí)后初始化,這是一個(gè)問(wèn)題。
在 C++ 的語(yǔ)言級(jí)解決方案中,使用的是單件模塊。要么由鏈接器決定以怎樣的次序來(lái)生成初始化代碼,這,通常會(huì)因?yàn)橐蕾?lài)關(guān)系和實(shí)際構(gòu)造次序不同而導(dǎo)致 bug (注:我在某幾本 C++ 書(shū)中都見(jiàn)過(guò),待核實(shí)。自己好久不寫(xiě) C++ 也沒(méi)有實(shí)際的錯(cuò)誤例子);要么使用惰性初始化方案。這個(gè)惰性初始化也不是萬(wàn)能的,并且有些額外的開(kāi)銷(xiāo)。(多線程環(huán)境中尤其需要注意)
我使用 C 語(yǔ)言做初期設(shè)計(jì)的時(shí)候,采用的是一種足夠簡(jiǎn)單的方法。就是,以編碼規(guī)范來(lái)規(guī)定,每個(gè)模塊必須存在一個(gè)初始化函數(shù),有規(guī)范的名字。比如 foo 模塊的初始化入口叫
- int foo_init()
規(guī)定:凡使用特定模塊,必須調(diào)用模塊初始化函數(shù)。
為了避免模塊重復(fù)初始化,初始化函數(shù)并不直接調(diào)用,而是間接的。類(lèi)似這樣:
- mod_using(foo_init);
mod_using 負(fù)責(zé)調(diào)用初始化函數(shù),并保證不重復(fù)調(diào)用,也可以檢查循環(huán)依賴(lài)。
在這里,我們還約定了初始化成功于否的返回值。(在我們的系統(tǒng)中,返回 0 表示正確,1 表示失?。┤缓蠖x了一個(gè)宏來(lái)做這個(gè)使用。
- #define USING(m) if (mod_using(m##_init,#m)) { return 1; }
注:我個(gè)人反對(duì)濫用宏。也盡可能的避免它。這里使用宏,經(jīng)過(guò)了慎重的考慮。我希望可以有一個(gè)代碼掃描器去判斷我是否漏掉了模塊初始化(可能我使用了一個(gè)模塊,但忘記初始化它)。宏可以幫助代碼掃描分析器更容易實(shí)現(xiàn)。而且,使用宏更像是對(duì)語(yǔ)言做的輕微且必要的擴(kuò)展。
這樣,我的系統(tǒng)中模塊模塊的實(shí)現(xiàn)代碼最后,都有一個(gè) init 函數(shù),里面只是簡(jiǎn)單的調(diào)用了 USING 來(lái)引用別的模塊。例如:
- #include "module.h"
- /*
- 我個(gè)人偏愛(ài)把 module.h 的引入放在源文件最后,初始化入口之前。
- 它里面之定義了 USING 宏,以及相關(guān)管理函數(shù)。
- 這樣做是為了避免在代碼的其它地方去引入別的模塊。
- */
- int
- foo_init()
- {
- USING(memory); // 引用內(nèi)存管理模塊
- USING(log); // 引用 log 模塊
- return 0;
- }
至于模塊的卸載,大部分需求下是不需要的。今天在這里就不論證這一點(diǎn)了。
原文鏈接:http://blog.codingnow.com/2010/01/c_modularization.html
【編輯推薦】