程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 多任務開發C++風格討論

多任務開發C++風格討論

編輯:關於C語言

陳碩先生是位資深的C++工程師,其對C++領域知識的掌握,令我歎服。 他的博客在這裡: http://blog.csdn.net/Solstice 日前,陳先生作為我的新書《0 bug - C/C++商用工程之道》的讀者,在豆瓣網對本書做出了客觀的評價,無論是批評還是認同,都令我非常感動,我也做了認真的回復。 我感覺,我們之間的一些認知差異,其實主要還是根據不同的開發角度理解不同,大家在很多理念上還是有很大的共識,我們之間的討論,對於初學C和C++的朋友,特別是嵌入式,服務器開發類別的,有多線程多任務開發需求的程序員朋友,有一定借鑒作用。 因此,在征得陳先生同意的前提下,我把我們溝通的細節拷貝到這裡,供大家參考。 陳先生原文:   大致讀了一遍,說幾點體會。 
 
這本書前半部分主要講編程風格,後半部分介紹了作者自己多年積累的一些程序庫。 
 
編程風格見仁見智,我喜歡作者只用 for (int i = 0; i < n; ++i) 循環《程序設計實踐》也是這麼提倡的),但不喜歡像他那樣使用 goto 和宏。在 C++ 裡,goto 不只是跳過幾行語句跳出幾層嵌套那麼簡單,還涉及對象的初始化,而 goto 不能跨越初始化,編譯器會報錯。在 C 語言裡使用 goto 或許還可以接受,C++ 裡不行。 
 
這本書把代碼用無襯線非等寬字體大概是 Arial 之類)印在灰色底紋上,讀起來很費眼。我只認真讀了第 6 章《鎖》的代碼,因為我對多線程編程比較熟悉。後面幾章的內存池和隊列等沒有細看,只大致浏覽了一下。通讀整本書的代碼,有幾點我很喜歡,第一是沒有用異常,第二是沒有用繼承也就沒有虛函數、設計模式這些東西),第三是只出現過一個類模板,代碼見 http://blog.csdn.net
/tonyxiaohome/archive/2010/01/03/5124521.aspx 
 
也有幾點我不喜歡,整本書的代碼基本上都是披著 C++ 外衣馬甲)的 C 代碼,作者多次先用 C 語言實現某個功能,再用 C++ 簡單封裝一下。整體代碼風格有 90 年代中期用 Borland C++ 開發的 C/C++ 程序的感覺:幾乎沒有見到 C++ 標准庫的使用,只使用了少量 C 的標准庫strcpy/memcpy/vsnprintf 之類,從第 8.3.5 節看來 memmove 似乎被遺忘了),喜歡自己寫一套,大量重新發明輪子。C++ 語言的使用基本停留在十年前的水准,與當前 C++ 社區推崇的實踐風格我不是指模板元編程那一套,而是指 RAII)相去較遠,連最基本的成員初始化列表和 const 修飾符都很少用到。這樣的代碼風格,在我們組肯定會被斃掉的。 
 
具體說說第 6 章《鎖》。作者自己實現了一個讀寫鎖,並命名為“單寫多讀鎖”。說實話我很佩服作者的聰明和勇氣。另外我很好奇——除了作者本人和他的團隊,誰敢把這段代碼用到產品中。如果這段代碼是正確的,那麼它的效率是否比操作系統提供的讀寫鎖據我所知,pthreads 和 Windows Vista/7 直接提供了讀寫鎖 API)更好?如果它的效率比系統的讀寫鎖更好,如何證明它是正確的,會不會遺漏了什麼邊界條件或 race condition 沒有考慮?這些是我的疑慮。 
 
我的體會是,搞多線程編程如履薄冰,千萬別自己發明東西,那將幾乎肯定是錯的。不說別的,單是一個 Singleton 模式的線程安全實現就能難倒很多人。一度人們認為 Double checked locking 是王道,兼顧了效率與正確性。後來有神牛指出由於亂序執行的影響,DCL 是靠不住的。這個又讓我想起了 SQL 注入,十年前用字符串拼接出 SQL 語句是 Web 開發的通行做法,直到有一天有人利用這個漏洞越權獲得並修改網站數據,人們才幡然醒悟,趕緊修補。)Java 開發者還算幸運,可以借助內部靜態類的裝載來實現。C++ 就比較慘,要麼次次鎖,要麼 eager initialize、或者動用 memory barrier 這樣的大殺器 http://www.aristeia.
com/Papers/DDJ_Jul_Aug_2004_revised.pdf )。接下來 Java 5 修訂了內存模型,並增強了 volatile 的語義,這下 DCL (with volatile) 又是安全的了。然而 C++ 的內存模型還在修訂中,C++ 的 volatile 目前還不能將來也難說)保證 DCL 的正確性只在 VS2005+ 上有效)。 
 
舉這個例子,是想說明編寫線程安全的代碼遑論實現線程同步原語)是件多麼困難的事情。開發者需要深入理解多 CPU 下的內存模型、亂序、cache 一致性與 memory barrier、原子操作、各種常見陷阱等等,才不會重蹈覆轍。作為一般開發人員——或者如作者所說,商業程序員——最好使用成熟的經過大量人群反復驗證的庫和 idioms,而不要試圖自己發明輪子,特別是這種極不容易造好的輪子。如果形勢所迫非造不可,比如當前系統沒有直接提供讀寫鎖Windows XP 及以下就沒有),而項目又要用到,那麼移植一個現有的實現無論是 pthreads 或者 Java 或者其他開源項目比如 ACE)或許是個更好的思路,而不是靠自己的聰明才智去發明讀寫鎖算法。 
 
這本書第 6 章用互斥器實現了線程安全的 CMInt 和 CMBool 這兩個類。我認為這完全沒必要,因為用原子操作 (Windows 下是 _InterlockedIncrement 等) 就能達到同樣的效果,而且效率只會更高。另一方面,更重要的是,CMInt 和 CMBool 由於包含了互斥器,那麼拷貝構造和賦值操作符應該是被禁掉的,否則兩個對象可能意外地共享同一個鎖,這會導致難以預料的行為。作者通篇似乎沒有考慮拷貝構造和賦值操作符的存在,比如書中的 CMutexLock 竟然是可以拷貝構造和賦值的,這直接會導致多重銷毀。C++ 代碼只寫普通構造函數和析構函數而忽視 copy-ctor 和 assignment,這恐怕很難算是合格的 C++ 程序員我相信作者是個合格的 C 程序員),畢竟這是《Effective C++》上反復強調的內容。 
 
作者在第 6.1.4.6 節提到在析構函數裡額外做一次加鎖和解鎖,防止程序在多線程下崩潰,這更是錯誤的,因為對象的生與死不能由該對象自身擁有的互斥器來保護。這個問題很深,但解決起來並不費勁。2009 年 12 月的上海C++技術大會上有一場《當析構函數遇到多線程》的主題演講,將來有空我會把演講稿整理成文放到博客上。 
 
這本書或許能從一個側面反映國內 C++ 開發的大體水平。C++ 照作者這麼用,固然不符合我的審美和我們團隊的性能要求,但也不妨礙做出質量性能合格、能賣出去的軟件。特別是最後幾章談到抓內存洩漏和 Sockets 洩漏,雖說辦法土,倒也是挺奏效的。說實話,我挺佩服作者在缺乏工具的情況下自己設法制造工具解決問題的能力。 
 
我不後悔買了這本書。總的來說,這本書值得去讀,可以以很低的代價了解別人和別的公司在工作中是怎麼做的。這些做法夠不夠好,是不是能更好,自己遇到了如何解決?這又能引發思考,並提供了很多討論話題。

我的回復:
嗯,我也說幾句。 
幾位大牛確實一針見血,說出了我很多缺點和弱點,在此表示感謝。 

嗯,我還是申辯幾句哈。 
沒有完全使用C++,固然有我本人水平不夠的影子,呵呵,我自我評價,我是標准的山寨C++程序員,嘿嘿。 

但,中間大牛們百思不得其解的一個奇怪寫法,我這裡解釋一下。 
為什麼不去直接用C++的類封裝,而是費盡心機寫了一個結構體封裝類成員數據,然後寫一堆C函數,然後再用類來封裝。這是有原因,原因呢,書裡面也寫了。 
關鍵是為了預防內存碎片。 
C和C++的動態內存分配機制,有個問題,長期,大量申請和釋放後,會有內存碎片,嚴重的會導致服務器掛死,而我開發的很多工程,都有7*24小時工作需求,因此,內存碎片普通程序員可能都感覺不到,卻成了我必須解決的難題。 
因此我做了個內存池,主要是基於重用理念,仿造STL的內存池構建方法,建立自己的多線程安全內存池。 

但是,這個內存池有個缺點,只能提供malloc和free服務,不具備new和delete操作,我編譯原理學得不好,確實不知道怎麼做,呵呵。 
這就說明,我的內存池,只能為純數據區塊服務,無法應對動態對象的申請和釋放。 

但是,C++的對象在大規模工程組織中的便利性我又捨不得放棄,沒辦法,我就做了個奇奇怪怪的方法。 
對於某些高頻申請和釋放,且長度不等的應用需求,比如,傳輸服務器常見的動態哈希表,用於目標地址,或者一些報文的快速存放和取出的工具,我習慣於這麼做。 
1、類裡面手工區別成員變量和成員函數,先用C的struct實現成員變量組,然後用純C方式提供訪問邏輯。 
2、用類封裝,封裝時,內部所有成員變量利用一個struct指針來表示,這時候,這個struct可以在內存池上分配。 
我們知道,通常一個對象的絕對尺寸,主要還是取決與內部的數據,函數表占不了多少字節的,幾十個字節頂天了,當占主要尺寸的內部數據區使用內存池管理,規避了內存碎片之後,幾十個字節的對象,一般說來,隨便new和delete,都不會再有內存碎片的風險了。 
這實際上是根據實際使用需求,強行拆裂C++對象中數據與方法的集合,使之分別從不同的內存區申請內存,以實現盡可能的運行安全性,避免內存碎片的影響。當然,這裡面還有個用以,我做工程的環境,很多時候不一定是C++環境,有可能某些時候,就碰到純C的環境,這麼開發的話,很多基本的工具,我可以同時提供純C和C++兩套api,調用更加方便。

主要目的就是這個,不知道我講清楚了沒有。 
這是實戰出來的經驗,因為過去很多次,我的服務器就是因為這個原因掛掉的,而且,很不好找原因。 

嗯,再說一點,原子鎖我研究過,不過,我發現,原子鎖和操作系統關系太密切,我要求我的庫是跨平台的,嗯,這個就不勞大家批評了,這是我的設計需求。 
因此,我一般不太喜歡沾操作系統太多的東西,所以,我在鎖上面又做了一層封裝,實現了跨平台的安全調用機制,至少從我的工作中,這種努力時值得的,因為我通常同時寫一個傳輸協議的客戶端和服務器端,而客戶端使用Windows平台,服務器端是Linux平台,二者還都有可能是arm linux平台。 
這麼寫,看似效率不高,很怪異,但總好過我同時維護幾套庫。大家說是不是? 

嗯,析構函數內無意義的加鎖和解鎖,我是作為一個小經驗分享的,叫做有它更好,沒有也無所謂。 
近年來,我使用標准成員函數定名的方式,以Start和Stop作為一個包含線程的類的顯示起停標志,並且使用防止重入鎖規避重入錯誤,已經是很成熟的編程手法。 
目前我開發時,析構函數習慣性調用Stop,確保本類所屬所有線程均已經安全退出再釋放,其實已經足以保證安全性了,嗯,書上的這個無意義加鎖和解鎖,其實在很多時候沒必要。 
我提出這個經驗值,主要是考慮到,有些純工具函數類,可能被n個線程調用,如果程序員不小心,可能會在析構對象時,會導致還有線程沒有退出,在訪問這個對象,導致掛死。 
由於這種情況往往多發生在程序退出時,其實就是有個時間差,也許由於程序員不小心,會有一些線程多活幾個周期,多碰幾次這個對象,這個,我能避免,但不是所有的程序員都能避免。 
因此,我介紹這個經驗,就是盡最大可能,幫助程序員在沒有控制好的時候,能規避一點,就規避一點。 
我也同意,這不是解決問題正確之道,但,有時候能救命,呵呵。 
 
最後我還是要說,我僅僅是一個普通程序員,書中是我自己的一點實戰經驗,理論可能不一定全對,不過,幾乎每個知識點,都是經過bug總結出來的,背後,往往都是十天半個月的加班,痛定思痛,總結一點,呵呵,說經驗,都還不如說教訓比較准確。 
我從來沒有認為自己有能力代表國內程序員的水平,我甚至連我們公司都代表不了,我僅僅代表我自己。 
 
最後,還是謝謝各位大牛的關注,如果有問題,歡迎繼續討論。
 
補充一點,goto的使用見仁見智,我堅持我的看法。 
在目前並行開發的環境下,很多時候,我們會碰到二元動作。 
malloc-free 
new-delete 
lock-unlock 
。。。 
尤其是由於鎖的加入,二元動作嵌套時,是不允許跨大括號跳出的,必須層級跳出,即嵌套多少層,就要跳躍多少次。這些都帶來了編程的麻煩。 
因此,我有個總結,多任務開發環境下,對於跳轉的精准控制要求到了一個變態的地步。 
傳統結構化程序設計,由於起源比較早,提出的很多理念,是基於單任務開發環境下總結的,對於多任務環境,不盡適用。這個goto我認為就是一例。 
結構化程序設計,強調實用break和continue來代替goto,但由於這二者都沒有能力精確標定落點,開發中起到的效果,很多時候還不如goto。 

所以,我堅持,goto不是不能用,但是不要亂用。 
我書中講過上述道理的。 
嗯,宏是個人習慣,我書中強調,我既不說宏好,也不說不好,請各位讀者自己選用。 
不知道我解釋清楚沒有?
 
陳先生email回復: 我已經拜讀了,您表述得很清楚,我就不在那兒回復了。那個帖子已經夠長,隨時有偏題的可能。 說實話,在網絡論壇上你一言我一語地來回發帖,不如在email裡慢慢說個透。每個人的背景和關 注點不盡相同,思考的角度不同,同樣的問題選用的解決方案自然也不同。
體到您回復中提到“這個內存池有個缺點,只能提供malloc和free服務,不具備new和delete操作”,我 知道兩個做法,可供參考。
第一個是在 class 裡重載 operator new / operator delete,這個我不推薦,因為這等於隱藏了“對象是從 內存池分配”這一事實。
第二個辦法是用 placement new ,比如代碼隨手寫的,可能有錯)
這是內存池 class MemoryPool : boost::noncopyable {  public:   void* alloc(size_t length);  // 相當於 malloc,返回的地址是充分對齊的   void dealloc(void*); // 相當於 free };
我有一個 class,希望在池上創建 class BigInt { };
BigInt* p = new BigInt; // 這肯定是不行的,用不到內存池
借助 placement new
void* memory = memoryPool.alloc(sizeof(BigInt)); // 先從池上分配一塊內存 BigInt* p = new (memory) BigInt; // 再在這塊內存上創建對象
釋放的時候不能直接 delete p,會 core dump,需要:
p->~BigInt(); // 手工調用析構函數 memoryPool.dealloc(p); // 把內存放回池子
當然,每次都這麼做會很容易出錯,一個稍好的辦法是定義兩個全局函數封裝這兩步:
BigInt* newBigIntFromPool(MemoryPool* m); deleteBigIntFromPool(BigInt* p);
當然,每個 class 都要定義兩個額外的函數,這也是個不小的負擔,於是我們可以用 類模板來實現 Factory 模式。
template<typename T> class PoolFactory : boost::noncopyable {  public:   explicit PoolFactory(MemoryPool* pool) : pool_(pool)   {   }
  T* newObject() const   {     void* p = pool_->alloc(sizeof(T));     assert(p != NULL);     return new (p) T();   }
  void deleteObject(T* p) const   {     assert(p != NULL);     p->~T();     pool_->dealloc(p);   }
 private:   MemoryPool* pool_; };
typedef PoolFactory<BigInt> BigIntFactory; // 這句放到每個具體 class 的定義之後
使用的時候,如果 memoryPool 全局只有一個且線程安全的話,對象的申請和釋放可以這麼寫: BigInt* p = BigIntFactory(&memoryPool).newObject(); // 構造 BigIntFactory(&memoryPool).deleteObject(p); // 釋放 這裡構造了臨時對象 BigIntFactory(&memoryPool) ,由於編譯期會把函數內聯展開, 並不會有任何額外開銷。
必要的話還可以定義兩個宏我很不喜歡宏)來減少重復,比如 #define NEW_FROM_POOL(TYPE, POOL) TYPE##Factory(&POOL).newObject() #define DELETE_FROM_POOL(TYPE, POOL, PTR) TYPE##Factory(&POOL).deleteObject(PTR) 用的時候 BigInt* p = NEW_FROM_POOL(BigInt, memoryPool); DELETE_FROM_POOL(BigInt, memoryPool, p);
這個 PoolFactory 借鑒了 STL allocator 的實現。雖然我懷疑 STL allocator 的存在價值, 不過學習它的代碼是沒啥壞處的。
我的回復:
嗯,謝謝你。
你回的很長,很認真,我需要仔細理解一下,暫時還在看。
 
我們做的是運營級服務器,IP語音的,屬於全球運營,因此是7*24連續工作,不允許關機的。
其實很多嵌入式設備也是這樣,如果你家裡有無限AP,或者用小路由器分網段,你可以想象一下,這些小家伙,是不是一直不斷電的。
你的方法直覺上是可行的,因為指定內存區塊去new一個對象,是可以的。
我之所以沒用,有兩個方面,一個是這對調用者有一定技術要求,api會比較有深度,我不敢保證我每個程序員都有這個技術實力,不會用錯。嗯,還有一個就是,arm-linux-gcc,貌似還有個arm-etc-gcc(沒用過,記不清了),這兩種編譯器,無法在小內存環境下正常編譯模板,其實我書中的多線程安全的變量模板,就是這個原因被廢了的。本來還是挺好用的一個東東,呵呵。因此,我有點怕在跨平台開發時,大規模使用C++的高階特性。
書中很多奇怪的設計,其實都是因為有這樣那樣的顧慮存在,導致被迫這麼做的。
 
嗯,多說一點我對宏的看法,原則上,公開場合,我一般不說宏的好話,呵呵,goto也說得少。
我之所以近幾年有多用宏的趨勢,是有點想向函數式開發靠攏的意思。宏我比較看重它固化計算的作用,而且,它不檢查變量類型嗯,這個有時候我覺得是優點),它的結果,取決與運行期計算結果,而不是預設值,這些其實都符合函數式編程的規范。
我也在探討,能不能用宏來整理一套C實現函數式編程的方法,起碼是具有某種函數式開發特性的方法。原因很簡單,函數式編程無變量,天生具有線程安全性,僅此一條性能,已經足以讓我流口水了,呵呵。
因此,很多時候,我在用宏時,腦子裡想的是函數式編程。
我個人建議哈,對一個東東,既不說一定好,也不說一定不好,我喜歡走起來看,什麼時候,什麼地點,合用,不妨用一點,不合用呢,不用好了。
 
嗯,有個問題我一直想補充,結果搞忘了。呵呵。
 
在我的團隊中,有個鐵律,永遠不准在非簡單變量之外的其他類型變量之間發生賦值動作。
說白了,就是對象不能等於對象。
原因很簡單,根據經驗,目前的C++程序員,大多數對這類拷貝構造函數操作都不好,不是說他們不會寫,是他們說不清楚,使用了拷貝構造函數,中間那個臨時對象的生命周期是多長,以及到底位於哪塊內存區域,這個臨時對象本身是不是線程安全的。這往往是造成bug的根源,而且,很不好查找。
我們規定,對象傳遞,一定使用傳址方式,不允許傳值。就是想從團隊規范上,規避這一大類bug。
如果code review的時候,我發現這種設計,通常是無條件打回重做。
通理,函數不允許返回對象,函數甚至不允許返回除了void,bool,int之外的其他變量類型,這也是非常嚴格的規避bug措施。
 
因此,類中拷貝構造函數,長期以來被我看做是制造bug的根源,和你不喜歡宏一樣,我直覺上就反感它。因此,我的設計中,不考慮拷貝構造函數這類設計,因為我們的設計規范中,不允許有用到它的地方。這好比線程ID一樣,我認為線程ID唯一的作用就是killthread,這簡直是教人犯罪,因此,我的程序原則上不保存線程ID,不給大家犯錯誤的機會。
 
指針一般的原則,是上層分配好,傳遞給下層使用,下層嚴禁申請對象或內存,反傳給上層使用,因為這往往是洩漏的開端。如果實在有這個設計需求,比如某個傳輸底層內核,收到報文是臨時分配的內存,需要通知給上層,我們的原則是能傳值則盡量傳值,比如拷貝到上層給出的一個buffer中,實在不能傳值,則使用回調,堅決遵守“誰申請,誰釋放”的原則。
 
指針使用還有一個禁忌,是我堅持得非常好的原則。“永遠不許修改指針的指向,只能修改指針指向地址的值”。
這是鐵律,n多的bug,其實都是因為大家把指針改來改去,最後發生了洩漏或野指針。因此,干脆從規范上予以禁止。
這句話的推論是,永遠在程序中,不允許存在兩個以上的*號,即**。
實在需要修改指針的值,比如某些CreateToken功能的函數,要創建一個規定格式的結構體什麼的,有兩個解決方案。

第一,仿造微軟辦法,設置P變量,然後顯式傳址調用,防止誤讀。
typedef strcut _TEST_STRUCT_
{
}*PSTestStruct,STestStruct;
void Func(PSTestStruct& pStruct)
{
}
能明白嗎?

第二,使用結構體封裝
typedef strcut _MALLOC_TEST_STRUCT_PARAM_
{
    STestStruct* m_pStruct;
}SMallocTestStructParam;
void Func(SMallocTestStructParam* pParam)
{
    pParam->pStruct=malloc(...);  //注意,這符合修改指針指向地址的值,而不是修改指針的值,這句話的指針,指SMallocTestStructParam*
}

以上是我開發服務器等高穩定性程序的一點團隊經驗,我想,既然你也做類似的工作,可能有用,不妨參考一下。
 
當然,我說得不一定全對,歡迎探討。
 
======================================================= 後面是一些IM即時溝通信息: 
肖舸其實有時候,我的庫很多都在為arm做讓步。   arm最多開30條線程,我才想到,要做個任務池,進一步抽象。   實現任務片段與線程割裂,達到大吞吐量的目的 

陳碩我是真正把C++當成一門與C不同的,高級的語言來用。也不用為移植性考慮,沒有ARM開發的需求。 

肖舸嗯,所以你當時說的我都認同,但是在我們這些草根環境下,很多情況沒有你的環境優秀,只有人將就機器了  C++對我最大的好處,其實就是對象的組織性,做大工程,真方便,我用C++,用的最多的就是這個 

陳碩任務池那一章我還沒有仔細看,既然我們都有 2440 的板子,或許可以一起研究研究,做點基礎工作。 

肖舸其實啊,這麼說吧,我書裡面的代碼,arm9都跑過的 
  
僅僅需要把線程池的最大線程數,從300降到30arm9就可以跑我的庫 
  
這是唯一一處移植修改 
  
我其實一直有個想法,這個工程庫可能以後最大的商業應用,其實是在嵌入式上面。
 
  肖舸  我裡面說了一個看法,C++並不是好的服務器開發語言 
  
嗯,包括嵌入式 
  
我不知道你能不能理解這句話 

陳碩如果你強調7x24的話 

我沒有發現更好的工業化語言,在服務器開發方面,java 或許能滿足一定的需求 
肖舸  其實我最欣賞你的,是你反對異常 你能說說為什麼反對嗎?我看看和我理由一樣不? 

陳碩我覺得在 C++ 裡亂拋異常就跟隨處大小便一樣可惡。C++ 的異常與整個語言風格格格不入,是個怪胎,異常加入 C++ 的時間也很晚,比模板還晚,目前還沒有形成公認的好的實踐。 

肖舸嗯,你的是一個解釋 
  
我來說說我的解釋,你看同意不 
  
我在做程序,強調分層,業務層和功能層 
  
層與層之間,以api或者npi松散耦合 
  api
CC++中,通常被定義在h文件裡面 
  
但是,由於異常的加入,在h中很難精確標明一個函數的所有交互手段 
  
即,我們即使看見了函數原型,都不足以表達該函數所有可能的異常 
  
這意味著,api模糊不清 
  
後續程序員在使用庫的時候,極其有可能因為某個異常沒有捕捉,導致崩潰 
  
而異常本身就是低概率時間,測試很難測試到 
  
因此,這種崩潰可能發生在運行期,而且,由於進程被操作系統kill 
  
導致debug信息無法跟蹤,該bug無法解決 
  ok ?

陳碩: ok, 我同意你的關點,C++異常較難成為API接口的一部分,這可以成為一篇博客。另外我補充一點,由於異常發生時棧會展開,如果因為外層沒有捕捉而崩潰,那麼就算有core dump,也不知道到底在哪兒觸發的異常。這個可以解決,但 C++ 默認的異常是不帶調用棧信息的 

肖舸你補充這點很重要   另外,npi的問題 
  
誰能見到一個異常可以跨機器傳遞? 
 
陳碩異常不但不能跨機器,也不能跨進程,還不能跨模塊,要正確使用太困難了,比寫對多線程程序還難,我水平有限,只好割愛不用了。 

肖舸對頭 
  
而且,異常,相當於沒有標明出發點的一次goto 

陳碩這個很形象 

肖舸很難在異常處理中,精確檢測當前程序的狀態,自然無法恢復現場 
  
異常,通常伴隨洩漏 
  
或者,double lock 

陳碩異常和洩漏這個倒是很容易解決,用析構函數封裝釋放操作就行,黑話叫RAII 

肖舸呵呵 
 嗯,我覺得異常不是好東西 

陳碩C++裡不好,在Java裡還行,不算差。 

肖舸我不喜歡別人打破我的程序流程,另起一個流程 
  我沒有研究過Java 
  
不過,Java既然支持多線程 
  
一定有鎖 
  
異常,如果拋出的是對象 
  
就是我說的,對象的拷貝構造函數概念,我很反感 
  
因為我不知道它是否線程安全 

陳碩: java  try {} catch {} finally {}, finally {} 裡邊能保證解鎖,這點比 C++ 優越。 

肖舸我還真不知道 
  
它為異常可以做個收攏的動作,嗯,不錯 

陳碩對,先不管你拋啥,我把我該做的做了,再接著來 

肖舸暈哦,如果C++有這個,我也許就不會這麼反對異常了 
  
嗯,我寫程序,誰申請誰釋放,是個規則 
  
但僅僅是一個推論 
  
其實根源是 
  
誰拉出來的屎,麻煩自己擦干淨 
  
呵呵 

陳碩呵呵,話糙理不糙,多線程下的對象生命期管理是個大問題,尤其是在C++裡。我正在寫一篇博客,仔細談談這個事情。 

肖舸嗯,其實我的書試圖解決的也是這個問題 

肖舸這麼說吧 
  
既然聊到這裡了 
  
我還是講點我的想法 
  
如果你看我的書 
  MBool
Mint,不管你贊不贊同,我建議你看看 
  
因為它們體現資源鎖概念 
  
其實這兩個類不重要,重要的是,我的資源鎖概念很實用 
  
另外,第九章,時間片,我私人建議你重點關注 
  
裡面有很多我理解的並行開發概念 

陳碩嗯,好的。 
  ok, 
等我把書從同事那裡拿回來一定認真拜讀 

肖舸就我目前理解,資源鎖是最能解決團隊bug 
  
我今天還在和同事討論這個問題,我給她的建議就是,資源鎖 

陳碩: MBool MInt 等於把每個成員函數的起止都用鎖框住了,類似Java裡的synchronized 方法 

肖舸哎,對了,麻煩你一件事 
  
如果你在書裡面,發現bug 
  
請務必給我信息 
  
我好在重印時修訂 

陳碩: ok, 那我得認真再讀一遍了 

肖舸筆誤啊 
  
至於技術爭論,那等我們爭論完了再說 

陳碩呵呵,好說 
 前面我說異常不帶調用棧信息,這個比較好解決,在構造異常對象的時候,調用一次 backtrace() 就能記住調用棧。這個函數在 gcc 裡有 

肖舸: VC有沒? 
陳碩: VC 不是叫這個名字,程序也能主動查調用棧,我找找例子   VC 查調用棧的那個我找到了,有兩篇文章  
  http://www.codeproject.com/KB/applications/leakfinder.aspx
  http://www.codeproject.com/KB/threads/StackWalker.aspx?fid=202364&fr=26 
=======================================================
在線底價購買《0bug-C/C++商用工程之道》
直接點擊下面鏈接或拷貝到浏覽器地址欄)
http://s.click.taobao.com/t_3?&p=mm_13866629_0_0&n=23&l=http%3A%2F%2Fsearch8.taobao.com%2Fbrowse%2F0%2Fn-g%2Corvv64tborsvwmjvgawdkmbqgboq---g%2Cgaqge5lhebbs6qzlfmqmttgtyo42jm6m22xllqa-------------1%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%2C11%2C12%2C13%2C14%2C15%2C16%2C17%2C18%2C19%2C20---40--coefp-0-all-0.htm%3Fpid%3Dmm_13866629_0_0
肖舸

本文出自 “肖舸的blog” 博客,請務必保留此出處http://tonyxiaohome.blog.51cto.com/925273/260742

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved