C語言大型項目中那些結構體是如何定下來,操作系統里有些結構體特別複雜,好多指針指向鏈表,他們如何設計?


結構體的設計主要考慮幾個問題:

1. 編譯環境無關,主要就是sizeof問題,盡量用typedef過的類型,比如DWORD uint32_t,可以準確控制大小,另外位元組對齊也要考慮,最好用#pragma pack也指定對齊方式,可以做到兼容32 64位。

2. 節約空間,聲明的順序要想好,最好按照位元組對齊擺放,如果你非要一個 char,一個int地間隔聲明就會浪費很多空間。另外用 數組還是指針加長度,要好好考慮下,在可預見的未來長度有限就用數組,否則指針加長度,指針加長度畢竟會導致多一塊內存分配,而且這樣結構體不能簡單地通過memcpy複製了,要慎重。如果有好多bool型的值,可以考慮用位來表示 struct { int x:1, int y:1 int z:1 },或者聲明成DWORD,然後用按位聲明的值去或出來,比如 Windows style, WS_VISIBLE | WS_CAPTION

3. 前後兼容,如果未來可能會修改的,最好在結構體開頭放一個 version,表示版本,使用者要注意檢查版本號,可以保持前後兼容,修改了聲明就改下版本號。Windows的做法是放一個cbSize,反正通常改結構體的話sizeof也是會變的。保留幾個 reserve供未來使用也可以。

4. 命名,我喜歡給結構體一個統一的前綴,而且全大寫,讓人一眼就看出這是個結構,和其他的class interface區分開。


剛開始定義幾個預習設計的數據成員,可能是其他模塊需要的數據結構。然後開始寫代碼,寫著寫著,需要什麼新數據成員,再往結構體裡面加。慢慢的,後來發現結構體里的成員太多了,然後把可以將分類的成員放在一個新定義的結構體里。最後。很多結構體就像現在你看到的這樣。。。。

把同類型的結構體用鏈表串起來,方便查找而已。。


1、有充分研究的、有RFC的,如TCP協議棧之類,按照協議規定/論文定義。

2、沒有充分研究的,盡量研究充分,然後按照研究成果定義。

PS:研究可大可小。

關鍵思路是:問題分解。

每個模塊解決一個子問題,每個模塊對外數據結構恆定;模塊內部則可因地制宜。

舉例來說,設計一個可傳輸任意數據的協議,如TCP/IP協議,設計思路是:

A、定義TCP/IP協議本身需要的各種欄位

B、定義報頭長度、報文長度等欄位,以便用戶加入報文

C、設計代碼,完成投遞報文、保證報文完整性等功能

顯然,這裡僅僅解決了TCP/IP協議相關功能;用戶數據是什麼鬼樣子,我們根本就不在乎——只要我們設計一個機制,允許用戶在我們的數據報文中加入任意內容(不管這些內容數據結構有多複雜)就行了(但不允許動我們的報頭,否則會妨礙TCP/IP協議的正常功能)。

這個思路,就叫「依賴倒置」:不是「被服務模塊」根據自己的需要,去要求TCP/IP協議必須能傳什麼數據;而是TCP/IP協議給「被服務模塊」一個約定,只要滿足這個約定,就可以使用tcp/ip傳輸數據。

(對TCP協議來說,這個約定就是「任何連續存放的、指定有長度的位元組數據」:把滿足這個約定的數據提交給已經建立的tcp鏈路,它就保證能替你發出去)。

類似的,http協議,它本身就是TCP協議的載荷;同時,它也定義了自己的報文格式,規定了如何給自己附載入荷——如果你從tcp開始追,追到http,這數據結構自然是龐大、複雜、扯來扯去的。

再如,我們需要做一個進程調度器,假設每個進程需要在進程式控制制塊中保留一份自己的環境變數信息,那麼我們需要具體定義一個用來存儲環境變數的數據結構嗎?

答案是:不需要。只要我們定義一個指針,允許進程管理器或其它什麼模塊,通過這個指針,把環境變數掛接進來就是了。

——至於指針類型,可以是void *,這可以允許不同的進程管理器自定義自己的環境變數格式;也可以是某個具體的數據結構(但這個數據結構原始定義可能是void*,將來進程管理器組開始工作時,再修改成具體的數據結構)。

明白了嗎?

具體完成TCP/IP或進程調度功能的模塊,使用的數據結構是相對簡單、單一的;只是根據需要,它們可能允許其它模塊在自己的數據結構中附加內容,只是對附加內容的格式可能有所要求而已。

正是這種附加協議,製造出了那些跨N個模塊、層巒疊嶂的、巨大無比、複雜無比的數據結構。

如果沒有模塊概念,貿然去分析這種數據結構,肯定是越看越暈;貿然去寫程序,肯定也是越弄越複雜,越弄越寫不下去。

正確的分析方法,應該是先找到每個子模塊,查看每個子模塊的輸入輸出數據結構,弄明白每個模塊的功能,並注意不同模塊之間的數據是通過什麼協議交互、掛接的。

——設計類似,也是先找出子模塊,定義模塊功能和輸入輸出數據;對那些需要對外提供服務的模塊,則可能就要設計類似附加指針、預留空白欄位、預留數據結構等方式,允許其它模塊附加「載荷」

正是「附載入荷」這個需求,弄出了那些看起來東拉西扯、龐大無比的數據結構:但,當你把載荷看做載荷時,好的設計一定是非常簡潔的。


C的大型項目中的設計需要注意的很多。系統寫,估計可以寫書了……這裡就羅列點目前能想到的。(都是血淚哦)

先說說題主說的數據結構方面的:

  • 工具結構體一定要和業務結構體脫離。
  • 任何結構體內的函數指針一定需要有顯式聲明。並設置相關warning為打開。
  • 多實例結構體,如果不得不用鏈表實現,那麼最好能實現線程安全的讀插刪介面。不得直接訪問結構體指針。
  • Summer Mr 的答案是正確的。早期一定在配置類的結構體內,保留冗餘空間。
  • 如果是RTOS or 無操作系統的大中型項目。數據結構需要根據不同的MCU做相應優化。

再說點其他的:

  • 頭文件的引用順序最好有專人負責。
  • 開關類宏,一定要有良好的測試用例。
  • 如果和資料庫打交道,專用存取者的線程一定要乾淨明了,不可以摻雜哪怕是個小轉義的業務邏輯。
  • 全局變數的實現體位置一定要慎之又慎。大型項目中,一個g.c申明所有全局變數的方法是不可取的(雖然小項目用這個很舒服)。
  • 源碼文件命名、目錄結構要有清晰文檔說明。
  • makefile最好專人負責。尤其是dep相關的……
  • 絕對需要做到每日版本。能自動build當然就更好了。
  • 版本迭代如果不仔細,絕對是災難前奏……
  • 開掉任何偷懶不進行單元測試的人。
  • 所有注釋使用英語。如果真難實現,那就有專用腳本將所有代碼文件轉為UTF8。
  • 日誌、日誌。在不影響開發性能情況下,debug日誌要豐富、好用。最起碼能有二進位RAW數據導出。


首先確定選擇何種設計模式和演算法,不同的設計模式要求不同的數據結構,然後進行代碼分層,確定各層的工作,從這些限制和依賴上倒推出核心數據結構,最後加入一些可擴展部分。


操作系統中的結構體設計,也要遵循結構體設計的一般原則噠。


推薦閱讀:

你當初為什麼選擇做設計師?
網站頁面設計怎麼平衡簡潔和複雜的關係?
Illustrator 怎麼做這樣的圖?
設計恐懼症,是否應該放棄建築設計方向的碩士進修以及從業方向?
做設計怎樣找靈感呢?

TAG:設計 | 操作系統 | 編程語言 | C編程語言 |