在學習C語言程序開發時,很多初學者常常會有一種「編程也不過如此」的錯覺,這種感覺通常出現在剛剛學完C語言語法,且能夠獨立完成一些課後練習題的時候,初學者的信心會在這一時期達到頂峰。可能會覺得程序無非就是各種 if 條件判斷,加上相應的邏輯處理。
程式設計師編寫程序就是為了服務人的,程序能夠提供的服務越多,這個程序的功能也就越強大。不過,程序是死板的,它需要接收外界(比如人)輸入的指令,才知道要做什麼。
例如,你打開瀏覽器,你得點擊我的文章才能看到這些內容。頭條不會在你沒有輸入時,自動的把我的文章顯示到手機。
這麼看來,「程序是各種條件判斷,加上相應的邏輯處理」這句話並沒有錯。程序會根據條件的不同,做出不同的響應。事實上,在C語言程序開發中也是如此——例如,程式設計師常常需要根據被調用函數不同的返回值,做出不同的處理,這其實就是「條件判斷」+「相應邏輯處理」。
不過,從上一節介紹的 3 種風格的C語言代碼應該可以看出,同樣一個功能,有經驗的程式設計師總是能夠寫出緊湊易讀的代碼,以更小的開銷,實現更高的執行效率。
以下這種C語言代碼常常出現在C語言程序開發中,請看:
if(cond){ ... statements; ...}else{ ... statements; ...}可是有時候 cond 只在極少的情況下發生,例如:隨機生成一個隨機數,該隨機數的範圍是 0~100000,如果隨機數小於 2,則將 val 賦值為 -1,否則將 val 賦值為當前UTC時間,相關C語言代碼如下,請看:
if(myrand() < 2) val = -1;else val = time(NULL);從上述代碼可以看出, val = -1; 其實只有 2/100000 的機率會被執行,但是為了這 2/100000 的機率,程序每次都需要判斷 if 條件是否成立,這會造成一定的性能損失。
可是,不寫 if 判斷代碼又會導致最終得到的C語言程序有可能不按照預期執行,該怎麼辦呢?類似的情況還有,某個條件非常可能成立,只會在極少情況下才不成立,但是同樣得寫上 if 語句每次判斷。
針對這種情況,其實可以參考 Linux 內核的C語言代碼,請看下面這兩個宏:
#define likely(x) __builtin_expect(!!(x), 1)#define unlikely(x) __builtin_expect(!!(x), 0)其實看宏的名字應該就能明白它們的作用:likely(x) 會告訴編譯器 x 很可能成立,unlikely(x) 則會告訴編譯器 x 不太可能成立,然後編譯器會據此優化代碼,生成效率更高的程序,稍後我們會看到一個實例。
這一過程是由編譯器內置函數__builtin_expect實現的,應該能夠發現,Linux 內核使用該函數時用到了一個小技巧——使用 「!!」 將條件轉換為 bool 值( 0 或 1)。
相信讀者應該已經明白,在C語言中任何非零值都會被認為是「真」,所以下面這樣的C語言代碼:
if(32) printf("true");elseprintf("false");編譯後會輸出 「true」。但是有時候有些程式設計師在開發中會忽略這一點,掉進「陷阱」,例如:
有兩個函數 fun1() 和 fun2() 會返回任意整數,要求只有當它們一個返回真,一個返回假的時候,才列印「success」。
有些程式設計師會直接寫:
int a = fun1();int b = fun2();if( ( a && (!b) || ( (!a)&&b ) ) printf("success")還有些程式設計師注意到了 fun1() 和 fun()2 要麼返回真,要麼返回假,他覺得上面這種寫法太羅嗦,於是寫可能會寫出這樣的C語言代碼:
if( fun1() != fun2() ) printf("success");看起來,似乎只有一個真一個假的時候,fun1() 和 fun2() 才會不相等,所以上面這種簡潔的寫法更好?
不過要是 fun1() 和 fun2() 函數一個返回 3,一個返回 4 ,上面這種寫法就會輸出不符合預期的結果了。所以這種思路正確的寫法如下,請看相關C語言代碼:
if( !!fun1() != !!fun2() ) printf("success");「!!」 可以將條件轉換為 bool 值,下面這兩種寫法是等價的:
b = cond?1:0;// 等價於b = !!cond;likely 與 unlikely 宏的實例
現在我們一起看一下 likely 與 unlikely 宏的作用,寫出C語言代碼如下,請看:
#define likely(x) (__builtin_expect(!!(x), 1))#define unlikely(x) (__builtin_expect(!!(x), 0))int test_likely(int x){ if(likely(x==0)) x = 6; else x = 9; return x;}int test_unlikely(int x){ if(unlikely(x==0)) x = 6; else x = 9; return x;}int test_normal(int x){ if(x==0) x = 6; else x = 9; return x;# gcc -fprofile-arcs -O2 -c t.c# objdump -d t.o
其實從這裡也能夠看出,如果程式設計師將 likely 宏與 unlikely 宏使用反了,是會降低C語言程序的效率的,因此在使用這兩個宏之前,一定要弄清楚條件是很大可能發生,還是基本不會發生,否則會適得其反。
小結
本節討論了C語言程序開發中條件語句的重要性,介紹了使用 「!!」 將條件轉換為 bool 值的小技巧,並在此基礎上討論了 Linux 內核中常用的 likely 和 unlikely 兩個宏,正確使用這兩個宏是能夠提高最終得到的C語言程序運行效率的。