簡體   English   中英

gcc是否會跳過此檢查以查找有符號整數溢出?

[英]Will gcc skip this check for signed integer overflow?

例如,給出以下代碼:

int f(int n)
{
    if (n < 0)
        return 0;
    n = n + 100;
    if (n < 0)
        return 0;
    return n;
}

假設您傳入的數字非常接近整數溢出(小於100),編譯器是否會生成會給您帶來負回報的代碼?

以下是Simon Tatham的“The Descent to C”中關於這個問題的摘錄:

“GNU C編譯器(gcc)為這個函數生成代碼,它可以返回一個負整數,如果你傳入(例如)最大表示能夠'int'的值。因為編譯器知道在第一個if語句之后n是正數,然后它假設不發生整數溢出,並使用該假設得出結論,在加法后n的值必須仍為正,因此它完全刪除第二個if語句並返回未選中的加法結果。

它讓我想知道C ++編譯器中是否存在同樣的問題,如果我不小心我的整數溢出檢查不會被跳過。

簡答

編譯器是否肯定會優化你的示例中的檢查,我們不能說所有情況,但我們可以使用帶有以下代碼的godbolt交互式編譯器gcc 4.9進行測試( 請參見實時 ):

int f(int n)
{
    if (n < 0) return 0;

    n = n + 100;

    if (n < 0) return 0;

    return n;
}

int f2(int n)
{
    if (n < 0) return 0;

    n = n + 100;

    return n;
}

並且我們看到它為兩個版本生成相同的代碼,這意味着它確實在第二次檢查時丟失:

f(int):  
    leal    100(%rdi), %eax #, tmp88 
    testl   %edi, %edi  # n
    movl    $0, %edx    #, tmp89
    cmovs   %edx, %eax  # tmp88,, tmp89, D.2246
    ret
f2(int):
    leal    100(%rdi), %eax #, tmp88
    testl   %edi, %edi  # n
    movl    $0, %edx    #, tmp89 
    cmovs   %edx, %eax  # tmp88,, tmp89, D.2249
    ret

答案很長

當您的代碼顯示未定義的行為或依賴於潛在的未定義行為( 在此示例中為有符號整數溢出 )然后是,編譯器可以進行假設並圍繞它們進行優化。 例如,它可以假設沒有未定義的行為,因此根據該假設進行優化。 最臭名昭着的例子可能是刪除Linux內核中的空檢查 代碼如下:

struct foo *s = ...;
int x = s->f;
if (!s) return ERROR;
... use s ..

使用的邏輯是,由於s被解除引用,它不能是空指針,否則將是未定義的行為,因此它優化了if (!s)檢查。 鏈接的文章說:

問題是第2行中s的取消引用允許編譯器推斷s不是null(如果指針為null則函數未定義;編譯器可以簡單地忽略這種情況)。 因此,第3行中的空檢查會被靜默優化,如果攻擊者能夠找到使用空指針調用此代碼的方法,則內核包含可利用的錯誤。

這同樣適用於C和C ++,它們都具有圍繞未定義行為的類似語言。 在這兩種情況下,標准都告訴我們未定義行為的結果是不可預測的,盡管兩種語言中具體未定義的結果可能不同。 C ++標准草案定義了未定義的行為,如下所示:

本國際標准沒有要求的行為

並包括以下注釋( 強調我的 ):

當本國際標准忽略任何明確的行為定義或程序使用錯誤的構造或錯誤數據時,可能會出現未定義的行為。 允許的未定義行為包括完全忽略不可預測的結果 ,在翻譯或程序執行期間以環境特征(有或沒有發出診斷消息)的特定行為,終止翻譯或執行(發布時)一條診斷信息)。 許多錯誤的程序結構不會產生未定義的行為; 他們需要被診斷出來。

C11標准草案有類似的語言。

正確的簽名溢出檢查

您的檢查不是防止有符號整數溢出的正確方法,您需要在執行操作之前進行檢查,如果導致溢出則不執行操作。 Cert有一個很好的參考 ,如何防止各種操作的有符號整數溢出。 對於附加案例,它建議如下:

#include <limits.h>

void f(signed int si_a, signed int si_b) {
  signed int sum;
  if (((si_b > 0) && (si_a > (INT_MAX - si_b))) ||
      ((si_b < 0) && (si_a < (INT_MIN - si_b)))) {
    /* Handle error */
  } else {
    sum = si_a + si_b;
  }

如果我們將這個代碼插入godbolt,我們可以看到檢查被省略了,這是我們期望的行為。

暫無
暫無

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

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