C++ 中的未定義行為

現(xiàn)在我們需要一個程序從控制臺讀入一個 INT 型整數(shù)(輸入確保是INT)资溃,然后輸出其絕對值即舌,你可能閉著眼睛就會寫出下面的代碼:

#include <iostream>

int main()
{
    int n;
    std::cin >> n;
    std::cout << abs(n) << std::endl;
}

等下啦粹,好好思考兩分鐘拼岳,然后寫幾個測試例子跑一下程序枝誊。那么你找出程序存在的問題了嗎?好了惜纸,歡迎走進未定義行為 (Undefined Behavior) 的世界叶撒。

未定義行為

什么是未定義行為

文章一開始的程序中用到了 abs 求絕對值函數(shù),當n為 INT_MIN 時耐版,函數(shù)返回什么呢祠够?C++ 標準中有這么一條:

If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.

在一個2進制系統(tǒng)中,當 n 是 INT_MIN 時粪牲,int abs(int n) 返回的值超出了 int 的范圍古瓤,所以這將導致一個未定義行為。很多時候,標準過于精煉落君,不便于我們快速查找穿香,因此我們可以在 cppreference 找到需要的信息,以 abs函數(shù)為例绎速,cppreference 明確指出可能導致未定義行為:

Computes the absolute value of an integer number. The behavior is undefined if the result cannot be represented by the return type.

那么到底什么是未定義行為呢皮获?簡單來說,就是某個操作邏輯上是不合法的纹冤,比如越界訪問數(shù)組等洒宝,但是C++ 標準并沒有告訴我們遇到這種情況該如何去處理。

我們知道在大部分語言(比如 Python 和 Java)中,一個語句要么按照我們的預期正確執(zhí)行,要么立即拋出異常狭归。但是在 C++ 中,還有一種情況就是将宪,某條語句并沒有按照預期執(zhí)行(邏輯上已經(jīng)出錯了)绘闷,但是程序還是可以繼續(xù)執(zhí)行(C++標準沒有告訴怎么繼續(xù)執(zhí)行)橡庞。只不過程序的行為已經(jīng)不可預測了,也就是說程序可能發(fā)生運行時錯誤印蔗,也可能給出錯誤的結(jié)果扒最,甚至還可能給出正確的結(jié)果。

有一點需要注意的是华嘹,對于有的未定義行為吧趣,現(xiàn)代編譯器有時候可以給出警告,或者是編譯失敗的提示信息耙厚。此外强挫,不同編譯器對于未定義行為的處理方式也不同。

常見的未定義行為

C++ 標準中有大量的未定義行為薛躬,如果在標準中查找 undefined behavior俯渤,將會看到幾十條相關(guān)內(nèi)容。如此眾多的未定義行為型宝,無疑給我們帶來了許多麻煩八匠,下面我們將列出一些常見的未定義行為,寫程序時應該盡量避免趴酣。

指針相關(guān)的常見未定義行為有如下內(nèi)容:

  • 解引用 nullptr 指針梨树;
  • 解引用一個未初始化的指針;
  • 解引用 new 操作失敗返回的指針岖寞;
  • 指針訪問越界(解引用一個超出數(shù)組邊界的指針)抡四;
  • 解引用一個指向已銷毀對象的指針;

解引用一個指向已銷毀對象的指針仗谆,有時候很容易就會犯這個錯誤指巡,例如在函數(shù)中返回局部指針地址跨释。 一些簡單的錯誤代碼如下:

#include <iostream>

int * get(int tmp){
    return &tmp;
}
int main()
{
    int *foo = get(10);
    std::cout << *foo << std::endl; // Undefined Behavior;

    int arr[] = {1,2,3,4};
    std::cout << *(arr+4) << std::endl; // Undefined Behavior;

    int *bar=0;
    *bar = 2;                       // Undefined Behavior;
    std::cout << *bar << std::endl;
    return 0;
}

其他常見未定義行為如下:

  • 有符號整數(shù)溢出(文章開頭的例子);
  • 整數(shù)做左移操作時厌处,移動的位數(shù)為負數(shù)鳖谈;
  • 整數(shù)做移位操作時,移動的位數(shù)超出整型占的位數(shù)阔涉。(int64_t i = 1; i <<= 72)缆娃;
  • 嘗試修改字符串字面值或者常量的內(nèi)容;
  • 對自動初始化且沒有賦初值的變量進行操作瑰排;(int i; i++; cout << i;)
  • 在有返回值的函數(shù)結(jié)束時不返回內(nèi)容贯要;

更完整的未定義行為列表可以在這里找到。

為什么存在未定義行為

C++ 程序經(jīng)常因為未定義行為而出現(xiàn)各種千奇百怪的 Bug椭住,調(diào)試起來也十分困難崇渗。相反,其他很多語言中并沒有未定義行為京郑,比如 python宅广,當訪問 list 越界時會拋出 list index out of range,這些語言中不會因為未定義行為出現(xiàn)各種奇怪的錯誤些举。那么為什么 C++ 標準為什么要搞這么多未定義行為呢跟狱?

原因是這樣可以簡化編譯器的工作,有時候還可以產(chǎn)生更加高效的代碼户魏。舉個例子來說驶臊,如果我們想讓解引用指針的操作行為變的明確起來(成功或者拋出異常),就需要在編譯期知道指針使用是否合法叼丑,那么編譯器至少需要做下面這些工作:

  • 檢查指針是否為 nullptr关翎;
  • 通過某種機制檢查指針保存的地址是否合法;
  • 通過某種機制拋出錯誤

這樣的話編譯器的實現(xiàn)會復雜很多鸠信。此外纵寝,如果我們有一個循環(huán)需要對大量的指針進行操作,那么編譯生成的代碼就會因為做各種附加檢查而導致效率低下症副。

實際上店雅,很多未定義行為,都是因為程序違反了某一先決條件而導致的贞铣,比如賦給指針的地址值必須是可訪問的闹啦,數(shù)組訪問時下標在正確的范圍內(nèi)。對 C++來說辕坝,語言設(shè)計者認為這是程序員(大家都是成年人了)需要保證的內(nèi)容窍奋,語言層面并不會去做相應的檢查。

不過,好消息是現(xiàn)在很多編譯器已經(jīng)可以診斷出一些可能導致未定義行為的操作琳袄,可以幫我們寫出更加健壯的程序江场。

其他一些行為

C++ 標準還規(guī)定了一些 Unspecified Behavior,一個簡單的例子(一個大公司曾經(jīng)的筆試題目)如下:

#include <iostream>
using namespace std;

int get(int i){
    cout << i << endl;
    return i+1;
}

int Cal(int a, int b) {
    return a+b;
}

int main() {
    cout << Cal(get(0), get(10)) << endl;
    return 0;
}

程序輸出多少窖逗?答案是視編譯器而定址否,可能是0 10 12,也可能是 10 0 12碎紊。這是因為函數(shù)參數(shù)的執(zhí)行順序是 Unspecified Behavior佑附,引用C++標準對 Unspecified Behavior 的說明:

Unspecified behavior use of an unspecified value, or other behavior where this International Standard provides two or more possibilities and imposes no further requirements on which is chosen in any instance.

此外,C++標準中還有所謂的 implementation-defined behavior仗考,比如C++標準說需要一個數(shù)據(jù)類型音同,然后具體的編譯器去選擇該類型占用的字節(jié)數(shù),或者是存儲方式(大端還是小端)秃嗜。

一般情況下权均,我們需要關(guān)心的只有未定義行為,因為它通常會導致程序出錯锅锨。而其他的兩種行為叽赊,不需要我們?nèi)リP(guān)心。

更多閱讀

Cppreference:Undefined behavior
What are all the common undefined behaviors that a C++ programmer should know about?
What are the common undefined/unspecified behavior for C that you run into?
function parameter evaluation order
A Guide to Undefined Behavior in C and C++, Part 1
A Guide to Undefined Behavior in C and C++, Part 2
Why is there so much undefined behavior in C++?
Cplusplus: abs
What Every C Programmer Should Know About Undefined Behavior
Undefined behavior and sequence points
Undefined, unspecified and implementation-defined behavior
Where do I find the current C or C++ standard documents?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末橡类,一起剝皮案震驚了整個濱河市蛇尚,隨后出現(xiàn)的幾起案子芽唇,更是在濱河造成了極大的恐慌顾画,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匆笤,死亡現(xiàn)場離奇詭異研侣,居然都是意外死亡,警方通過查閱死者的電腦和手機炮捧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門庶诡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咆课,你說我怎么就攤上這事末誓。” “怎么了书蚪?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵喇澡,是天一觀的道長。 經(jīng)常有香客問我殊校,道長晴玖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮呕屎,結(jié)果婚禮上让簿,老公的妹妹穿的比我還像新娘。我一直安慰自己秀睛,他們只是感情好尔当,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蹂安,像睡著了一般居凶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上藤抡,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天侠碧,我揣著相機與錄音,去河邊找鬼缠黍。 笑死弄兜,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的瓷式。 我是一名探鬼主播替饿,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼贸典!你這毒婦竟也來了视卢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤廊驼,失蹤者是張志新(化名)和其女友劉穎据过,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妒挎,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡绳锅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了酝掩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鳞芙。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖期虾,靈堂內(nèi)的尸體忽然破棺而出原朝,到底是詐尸還是另有隱情,我是刑警寧澤镶苞,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布喳坠,位于F島的核電站,受9級特大地震影響宾尚,放射性物質(zhì)發(fā)生泄漏丙笋。R本人自食惡果不足惜谢澈,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望御板。 院中可真熱鬧锥忿,春花似錦、人聲如沸怠肋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽笙各。三九已至钉答,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間杈抢,已是汗流浹背数尿。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惶楼,地道東北人右蹦。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像歼捐,于是被迫代替她去往敵國和親何陆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容