標籤:
關於C的未定義行為
轉自:http://www.guokr.com/blog/471312/
對於C的初學者來說,被要求做下面的這種題目真的是腦殘的不能再腦殘的行為。但是很多C初級教程——居然都有這樣的題。
最典型的例子就是
a+=a++;
這種情況下,a最後到底等於多少了?
編譯器應該如何理解a+=a++呢?首先是展開,a=a+a++;
然後分別計算a和a++的值,把它們相加,然後把結果賦給a。
但是這裡有一個問題,就是執行完a++之後,a++的值等於a本身的值,但是a的值卻變成了a+1。
所以關鍵是處理順序。
比如說int a=3;
如果編譯器先計算賦值號+=左邊a的值為3,然後計算右邊a++的值為3,同時a變為4。
然後計算3+3=6,賦給a,那麼a現在的值就是6。
如果編譯器先計算賦值號右邊的a++,得到的結果為3,同時a變為4,然後計算左邊a=4。
接著計算4+3=7,於是7這個數被賦值給了a。
也就是說,不同的理解方法,在這個例子裡面居然會得到不同的答案?
我為什麼要用居然?難道這個結果不是不可思議的嗎?一樣的運算式,只不過編譯器不一樣,就得出了不同的結果,這真是個悲劇啊。
難道沒有什麼標準要求編譯器採用相同的完整模式嗎?C語言的標準遵從ANSI C標準。但是很不幸,ANSI C標準裡面,並沒有關於遇到這種情況應如何處理的規定,反而是指出,編譯器你看著辦吧。
這就是C語言的“未定義行為”。
話說我只是有在用一個GCD函數的時候被某大神狠狠的吐槽了,這個GCD函數如下:
int GCD(int a,int b){ while (a %= b ^= a ^= b ^= a); return b;}
就像上面分析的那樣,這段程式在編譯過程中,會出現什麼順序,這也是標準裡面沒有規定的,屬於未定義行為。
所以用這個函數並不一定能保證得到正確的結果。
這種事情嘛。。。既然是交給編譯器的。
我想這個實際上應該是為了代碼最佳化。眾所周知,C是一個十分注重效率的語言,並且有那種為了效率放棄一切的感覺,不評價這個好不好,反正人家在最受歡迎的語言熱門排行榜第一位的寶座上坐了不知道多少年了。
給編譯器更大的自由,編譯器就能更好的最佳化產生的二進位代碼。
還有其他的方面,比如越界數組。
就像這樣
char str1[]="myworld";str1[18]=‘\0‘;
數組str1哪裡來的第19項啊!!!這種東西居然能通過編譯!!!
使用越界數組也是C的一個“未定義行為”。C的標準沒有規定編譯器在碰到這種情況應該怎麼做。這個時候編譯器的想法應該是——多一事不如少一事,我也不檢查這裡到底是不是這個數組的範圍了,反正你都叫我寫了,我就寫吧。
還有一個典型的操作就是允許一個隨便指的指標的讀寫。
比如我申請了一個動態地區,然後釋放掉了:
int *p;p=(int *)malloc(4*sizeof(int));/*各種對p的操作*/free(p);p[0]=0;printf("%d%d%d%d",p[0],p[1],p[2],p[3]);
毫不誇張的說,我自己的程式多次死在這種地方,就是free以後再print。。。
再比如:
int *p;p=0x1e642a80;p[8]=24;
這種指標操作居然也給通過????
更要命的是,上面的這些都屬於C的“未定義行為"就是說,雖然這些操作可以進行,但是編譯器並不保證執行結果。
就是說這種東西不但不報錯給通過了,而且還不按照我們想象的樣子執行,而是由著編譯器的性子隨便來?
從這個角度看,真的是太苦逼了。
我還碰到過一段腦殘代碼,類似這樣:
char tips[]="No";if(condition){ strcpy(tips,"Yes");}
對這種東西。。。。。呵呵。在Windows下運行就等著被中斷吧。。
還有使用未初始化的變數也是一種“未定義行為”,比如:
int x;printf("%d",x);
通常你也不知道你會在螢幕上看到什麼。。。。
GCC的第一版編譯器在碰到這種情況的時候,會在你螢幕上開始一個小遊戲。(這是開發組滿滿的惡意啊!!嗯,一定是!!)
C的變數並不會在聲明時(或第一次使用前)被初始化,這個特點飽受人們詬病。
不過ANSI C本身肯定是想通過省略這些初始化操作,來提升一點運行速度。
畢竟要初始化一個大數組或是用malloc分配的一大堆空間,還是挺費力的。。。
不過好處是……
不檢查數組邊界,不檢查指標指向地址的情況,不檢查強制類型轉換是否可以進行。全靠程式員程式的自覺,這點使得C的代碼效率會變得很高。
所以說,C雖然大量用於需要程式安全的場合,但是由於“未定義行為”的存在——C絕對不是一個安全的語言!!
但是更多的情況是,之所以這樣,所以才會更希望用C來實現。
還有一點需要說明的是。。
最開始那段:
int a=3;a+=a++;
幾乎所有的現代編譯器的結果都為7。
那個GCD的一行演算法,幾乎所有的現代編譯器都能正常運行。
雖然是“未定義演算法”,這個也算默默的達成了一種協議了吧。
雖然,使用它們仍然是危險的。
本文由飛翔的魚授權(果殼網)發表,文章著作權為原作者所有。
【轉】關於C的未定義行為