一:C標準庫中的 assert() 會粗暴地結束程式
assert()在發布版(release)的程式中被編譯為空白語句,它僅存在於調試版(debug)的程式中,它的意圖很明顯,就是及時提醒開發人員注意程式中的非正常情況,並輔助開發人員排除這種非正常情況,使程式逐步趨於完善。一般來說,一個assert()宣告失敗,必然是程式的運行狀態超出了程式員的預期,或程式流程進入到目前代碼尚未處理的一個分枝。在這種情況下,程式員要找出問題的根源並改進程式,就需要對assert()上下文進行分析。此時繼續向下逐步執行幾句代碼,可以搜集更多的資訊,有助於更及時的解決問題。可是“微軟公司提供的”C標準庫的assert()呢?宣告失敗後,程式並沒有中斷在調用assert()的那一行代碼處,而是中斷在C運行庫內部,而且程式馬上就要中止。以前用習慣了MFC中的 ASSERT(),它在宣告失敗後允許使用者選擇中止程式、忽略斷言、中斷(可以繼續執行),對程式員非常友好。現在用回C標準庫中的assert(),有點不能適應。解決方案是,可以自己寫一個 my_assert(),代碼如下:
//.h<br />#if _DEBUG || !NDEBUG<br />#define my_assert(exp) do{ if(!(exp)){my_assert_failed(#exp, __FILE__, __LINE__); __asm int 3} }while(0)<br />void my_assert_failed(const char* info, const char* file, unsigned int line);<br />#endif<br />#ifdef NDEBUG<br />#undef my_assert<br />#define my_assert(exp) (void*)(0)<br />#endif<br />//.c<br />#if _DEBUG || !NDEBUG<br />void my_assert_failed(const char* info, const char* file, unsigned int line)<br />{<br />printf("/007/nAssert failed: %s /n at file /"%s/", %d line. /n", info, file, line);<br />}<br />#endif<br />
二:for迴圈的簡單改寫竟然也出了問題
以下典型的for迴圈代碼,大家都是寫濫了的,熟的不能再熟了:
for(unsigned int i = 0; i < n; i++) { }
如果上面代碼中的 n 不是一個常量或變數,而是一個運算式的話(比如 p1->p2->n),那麼每迴圈一次,n就會被求一次值,無疑加重了程式負擔,在執行效率要求很高的情況下是不合適的。通常會把for迴圈稍加改寫,從後往前迴圈(適用於不介意迴圈次序的情況),迴圈變數 i 從 n-1 遞減至 0:
for(unsigned int i = n - 1; i >= 0; i--) { }
可是上面這種for迴圈的改寫有重大缺陷,就是當 n 為零時,其行為不是“不執行for迴圈”,而是“執行很多次for迴圈”。因為,當n為零時,迴圈變數 i 的初始值為 (unsigned int)-1,即4294967295,這個值大於零,因而for迴圈會一直執行,直到 i 遞減至0。這種行為與前面的常規for迴圈的行為(當n為零時不執行迴圈),迥異。關鍵在於 i 的類型是unsigned int,如果是int就不會有問題了(但是如果n是不帶正負號的整數,i定義成有符號整數一樣有大麻煩嘛)。代碼寫錯了容易改正,我所壓抑的是,找不到比較好的代碼書寫方式。我想到兩種方案:方案1,在for之前先把n求值並放到另一個變數m中,把迴圈寫為 for(unsigned int i =0; i < m; i++) {}。方案2,在上面第二種for迴圈(遞減i的那個)外面套一個判斷語句,if(n != 0) for(unsigned int i = n - 1; i >= 0; i--) {}。但總感覺都不是特別滿意。一般來說,方案1不錯了,是優選方案,可是它多用了一個變數,增大了棧空間佔用,對我這個嚴重依賴遞迴的程式不適合,最終無奈選擇了醜陋的方案2,但始終是心情壓抑,因為沒有寫出漂亮的代碼。天呐!我最後發現方案2也有重大缺陷,不帶正負號的整數類型的 i 永遠大於等於0,for迴圈永遠不會退出,死迴圈!無奈祭出方案3:for(unsigned int i = n; i > 0; i--) {},迴圈變數從n開始遞減到0(不包括0)。可是在方案3的迴圈內部,用到 i 的地方都要寫成 i - 1,依然額外增加了計算量(原來是每次迴圈對n重複求值,現在每次迴圈對 i - 1 求值),與改寫for迴圈的初衷不符。
寫不出漂亮的代碼就是不爽。我感覺自己是不是走火入魔了?在棧中定義一個變數多佔用4個位元組記憶體都不能接受?在迴圈中多計算一次加減法都不能接受?可同樣是我,用JAVA或易語言寫程式,絕對不會犯同樣的毛病。程式設計語言的魔力真的很神奇,很神奇。
下面隆重推出網友 zhxk82 在本文之後的評論中給出的另一種寫法:
for(unsigned int i = n - 1; i < (unsigned int)-1; i--)
哈哈,很不錯嘛,除了不是十分直觀之外,一切都很理想。哦,我現在已經不壓抑了,心情很舒暢。謝謝zhxk82。
2009.8.21淩晨liigo補記:誠如評論中有網友所說,第二條過於瑣碎了,太糾纏於細節。其實我也不想過早最佳化,也更傾向於寫直觀的代碼,不希望把事情複雜化,並且願意相信編譯器會處理的很好。最終我還是(有些不情願地)把代碼改回了最初的 for(unsigned int i = 0; i < n; i++),呵呵,然而還是有些情緒無法釋懷,嗨。
2009.8.22夜,liigo再次聲明:我寫此文並不是對C語言進行指責和抱怨,卻是為自己寫不出漂亮、簡單且直觀的代碼而自責(不知道各位有沒有同感,寫出漂亮的代碼會讓人心情舒暢)。文中表達了壓抑的心情,同時也是對心中壓抑情緒的釋放,是一種自我解脫方式。最後我跳出圈外,說出我多年來總結的稍微有些禪意的結論:當你使用者一個程式設計語言,出入於它的社區,就會受其思維模式的影響;而進入另一個領域時,相應地思維模式也會自動切換。思維模式的不同,會導致過程的不同和結果的不同。我相信這是一種比較接近於客觀的表述,不具有主觀傾向性。