簡體   English   中英

整數溢出是否會因內存損壞而導致未定義的行為?

[英]Does integer overflow cause undefined behavior because of memory corruption?

我最近讀到C和C ++中的帶符號整數溢出會導致未定義的行為:

如果在評估表達式期間,結果未在數學上定義或未在其類型的可表示值范圍內,則行為未定義。

我目前正試圖了解這里未定義行為的原因。 我認為這里發生了未定義的行為,因為當整數變得太大而無法適應底層類型時,整數開始操縱自身周圍的內存。

所以我決定在Visual Studio 2015中編寫一個小測試程序,用以下代碼測試該理論:

#include <stdio.h>
#include <limits.h>

struct TestStruct
{
    char pad1[50];
    int testVal;
    char pad2[50];
};

int main()
{
    TestStruct test;
    memset(&test, 0, sizeof(test));

    for (test.testVal = 0; ; test.testVal++)
    {
        if (test.testVal == INT_MAX)
            printf("Overflowing\r\n");
    }

    return 0;
}

我在這里使用了一個結構來防止Visual Studio在調試模式下的任何保護問題,比如堆棧變量的臨時填充等等。 無限循環應該導致test.testVal幾次溢出,並且確實如此,除了溢出本身之外沒有任何后果。

我在運行溢出測試時查看了內存轉儲,結果如下( test.testVal的內存地址為0x001CFAFC ):

0x001CFAE5  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x001CFAFC  94 53 ca d8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

帶內存轉儲的溢出整數

如你所見,int周圍的內存不斷溢出仍然“完好無損”。 我用類似的輸出測試了幾次。 從來沒有任何關於溢出的int損壞的內存。

這里發生了什么? 為什么變量test.testVal周圍的內存沒有受損? 這怎么會導致未定義的行為?

我試圖理解我的錯誤以及為什么在整數溢出期間沒有內存損壞。

你誤解了未定義行為的原因。 原因不是整數周圍的內存損壞 - 它總是占據整數占用的相同大小 - 而是基礎算術。

由於有符號整數不需要以2的補碼進行編碼,因此無法具體指導它們溢出時會發生什么。 不同的編碼或CPU行為可能導致不同的溢出結果,包括例如由於陷阱導致的程序殺死。

與所有未定義的行為一樣,即使您的硬件對其算法使用2的補碼並定義了溢出規則,編譯器也不受它們的約束。 例如,很長一段時間,GCC優化了任何只能在二進制補碼環境中實現的檢查。 例如, if (x > x + 1) f()要從優化代碼中刪除if (x > x + 1) f() ,因為有符號溢出是未定義的行為,這意味着它永遠不會發生(從編譯器的視圖來看,程序永遠不會包含產生未定義行為的代碼),意思是x永遠不會大於x + 1

標准的作者保留了未定義的整數溢出,因為某些硬件平台可能會陷入其后果可能無法預測的方式(可能包括隨機代碼執行和隨之而來的內存損壞)。 盡管具有可預測的靜音環繞溢出處理的二進制補充硬件在C89標准發布時已經成為標准(我已經檢查了許多可重編程微機架構,零使用其他任何東西)標准的作者不希望阻止任何人在舊機器上生成C實現。

在實現普通的二進制補充靜默環繞語義的實現上,代碼就像

int test(int x)
{
  int temp = (x==INT_MAX);
  if (x+1 <= 23) temp+=2;
  return temp;
}

當100%可靠時,在傳遞INT_MAX值時返回3,因為向INT_MAX添加1將產生INT_MIN,當然小於23。

在20世紀90年代,編譯器使用了這樣的事實:整數溢出是未定義的行為,而不是被定義為二進制補碼包裝,以實現各種優化,這意味着溢出的計算的確切結果將是不可預測的,但是行為方面沒有取決於確切的結果將留在軌道上。 給定上述代碼的20世紀90年代的編譯器可能會對它進行處理,好像在INT_MAX中添加1會產生一個大於INT_MAX的值,從而導致函數返回1而不是3,或者它可能像舊的編譯器一樣,產生3。在上面的代碼中,這樣的處理可以在許多平台上保存指令,因為(x + 1 <= 23)將等於(x <= 22)。 編譯器在選擇1或3時可能不一致,但生成的代碼除了產生其中一個值之外不會執行任何操作。

然而,從那以后,對於編譯器而言,使用標准的失敗對於程序行為的任何要求變得更加時髦,以防整數溢出(由硬件的存在導致的失敗,其后果可能是真正不可預測的)以證明編譯器的合理性。在溢出的情況下完全脫離軌道啟動代碼。 現代編譯器可能會注意到,如果x == INT_MAX,程序將調用未定義的行為,從而得出該函數永遠不會傳遞該值的結論。 如果函數永遠不會傳遞該值,則可以省略與INT_MAX的比較。 如果從x == INT_MAX的另一個翻譯單元調用上述函數,則它可能因此返回0或2; 如果從同一個翻譯單元中調用,效果可能會更奇怪,因為編譯器會將其關於x的推斷擴展回調用者。

關於溢出是否會導致內存損壞,在某些舊硬件上可能會有。 在現代硬件上運行的舊編譯器上,它不會。 在超現代編譯器中,溢出否定了時間和因果關系的結構,所以所有的賭注都是關閉的。 x + 1評估中的溢出可以有效地破壞先前與INT_MAX的比較所看到的x的值,使其表現得好像內存中的x值已被破壞。 此外,這種編譯器行為通常會刪除會阻止其他類型的內存損壞的條件邏輯,從而允許發生任意內存損壞。

未定義的行為未定義。 它可能會使您的程序崩潰。 它可能什么都不做。 它可能完全符合您的預期。 它可能會召喚鼻子惡魔。 它可能會刪除您的所有文件。 當遇到未定義的行為時,編譯器可以自由地發出它喜歡的任何代碼(或者根本不發出代碼)。

任何未定義行為的實例都會導致整個程序未定義 - 不僅僅是未定義的操作,因此編譯器可以對程序的任何部分執行任何操作。 包括時間旅行: 未定義的行為可能導致時間旅行(除其他外,但時間旅行是最有趣的)

有許多關於未定義行為的答案和博客文章,但以下是我的最愛。 如果您想了解有關該主題的更多信息,我建議您閱讀它們。

除了深奧的優化結果之外,您還必須考慮其他問題,即使您天真地希望生成非優化編譯器的代碼也是如此。

  • 即使您知道該體系結構是二進制補碼(或其他),溢出操作可能不會按預期設置標志,因此像if(a + b < 0)這樣的語句可能會采用錯誤的分支:給定兩個大的正數,所以當加在一起時它溢出並且結果,所以二元補充純粹主義者聲稱,是否定的,但加法指令可能實際上沒有設置負標志)

  • 多步操作可能發生在比sizeof(int)更寬的寄存器中,而不是在每一步都被截斷,因此像(x << 5) >> 5這樣的表達式可能不會像你一樣切斷左邊的五位假設他們願意。

  • 乘法和除法運算可以使用輔助寄存器來獲得產品和股息中的額外位。 如果乘法“不能”溢出,編譯器可以自由地假設輔助寄存器為零(或負產品為-1)並且在分割之前不重置它。 因此像x * y / z這樣的表達式可能會使用比預期更廣泛的中間產品。

其中一些聽起來像額外的准確性,但它的超精確性是預期的,無法預測或依賴,並且違反了你的心理模型“每個操作接受N位二進制補碼操作數並返回最不重要的N下一次操作的結果位“

C ++標准未定義整數溢出行為。 這意味着C ++的任何實現都可以隨意做任何事情。

在實踐中,這意味着:對於實現者來說最方便的是什么。 由於大多數實現者將int視為二進制補碼值,因此現在最常見的實現是兩個正數的溢出和是一個負數,它與真實結果有一定關系。 這是一個錯誤的答案 ,標准允許這樣做,因為標准允許任何內容。

有一種說法認為整數溢出應該被視為一個錯誤 ,就像整數除零一樣。 '86架構甚至有INTO指令在溢出時引發異常。 在某些時候,該論點可能會獲得足夠的權重,使其成為主流編譯器,此時整數溢出可能會導致崩潰。 這也符合C ++標准,它允許實現做任何事情。

您可以想象一種架構,其中數字以小端方式表示為以空字符結尾的字符串,零字節表示“數字結束”。 可以通過逐字節添加來完成添加,直到達到零字節。 在這樣的體系結構中,整數溢出可能會用一個覆蓋尾隨零,從而使得結果看起來更遠,更長並且可能在將來破壞數據。 這也符合C ++標准。

最后,正如其他一些回復中所指出的,大量的代碼生成和優化取決於編譯器對其生成的代碼及其執行方式的推理。 在整數溢出的情況下,編譯器完全合法(a)生成用於添加的代碼,其在添加大的正數時給出負結果;以及(b)通過添加大的正數來知道其代碼生成給出了積極的結果。 因此,例如

if (a+b>0) x=a+b;

可能,如果編譯器知道ab都是正數,沒有費心去執行測試,但無條件地將a添加到b並將結果放入x 在二進制補碼機器上,這可能導致將負值放入x ,這明顯違反了代碼的意圖。 這完全符合標准。

未定義int表示的值。 就像你想的那樣,記憶中沒有“溢出”。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM