2016-04-08 35 views
6

考慮簡單的代碼:雖然(i--)s + = a [i];在C和C++中包含未定義的行爲?

#include "stdio.h" 

#define N 10U 

int main() { 
    int a[N] = {0}; 
    unsigned int i = N; 
    int s = 0; 

    // Fill a 

    while(i--) 
     s += a[i]; 

    printf("Sum is %d\n", s); 

    return 0; 
} 

是否while循環包含未定義因爲整數下溢的行爲?編譯器是否有權假定while循環條件總是如此,並最終導致無限循環?

如果isigned int?它是否包含與數組訪問相關的陷阱?

更新

我跑這一點,類似的代碼很多次,它工作得很好。此外,它是向後遍歷數組和向量的流行方式。我問這個問題,以確保從標準的角度來看這種方式是可以的。

一眼看來,它顯然不是無限的。另一方面,有時編譯器可以「假設代碼不包含未定義的行爲」來「優化」一些條件和代碼。它會導致無限循環和其他不必要的後果。見this

+3

那麼,這是**不是**無限循環。 –

+2

當你運行它時發生了什麼? –

+0

沒問題,因爲'while(0)'是錯誤的,之後你不關心'i'的值 – MrTux

回答

6

由於整數下溢,while循環是否包含未定義的行爲?

不,在有符號整數的情況下,上溢/下溢只是未定義的行爲。

編譯器是否有權假定while循環的條件總是如此,並最終導致無限循環?

不,因爲表達式最終會變成零。

如果我簽署了int,該怎麼辦?它是否包含與數組訪問有關的陷阱?

如果它被簽名和溢出/下溢,則會調用未定義的行爲。

+0

's /只是未定義的行爲/只可能/' –

+0

@LightnessRacesinOrbit隨意向C標準委員會提交更正,3.4.3'「示例未定義行爲的示例是整數溢出行爲。 ' – Lundin

+1

該陳述是正確的。但是無符號值不會溢出,所以與這種情況無關。你的答案中的短語在技術上也是正確的,但我的觀點是,我認爲它提示了錯誤的事情 - 對於_unsigned_整數,溢出/下溢不是_defined_行爲,因爲它是不可能的! :)因此,我的建議是自然界的編輯。 –

9

此代碼不會調用未定義的行爲。一旦i變爲0,循環將被終止。對於unsigned int,不存在整數溢出/下溢。效果將與i相同signed除了在這種情況下不會包裝。

+0

爲什麼要投票? – haccks

+2

這是C++的標籤,對所有答案都有一個1/5的downvote比例。 (有一個+1來抵消。) –

3

i將繞回~00XFFFFFFFF爲32位),但循環將終止,因此沒有UB。

+0

「但循環將終止,因此沒有UB。」 - nononono,這是相反的方式。 **因爲**沒有UB,所以**編譯器必須發出使循環終止的代碼。反過來,定義行爲的原因是遞減的對象('i')是'unsigned'。 –

4

由於以下原因,循環不會產生未定義的行爲。

i初始化爲10,並在循環中遞減。當i的值爲零時,遞減該值將產生一個等於UINT_MAX的值 - unsigned可表示的最大值,但循環將終止。對於在N-1(即9)和0之間的i的值,將僅訪問(在循環內)a[i]。這些都是數組a中的所有有效指數。

s以及a的所有元素都被初始化爲零。因此,所有添加將0添加到0。這永遠不會溢出或下溢int,所以永遠不會導致未定義的行爲。

如果i更改爲signed int,則遞減永不下溢,並且當循環終止時i將具有負值。循環後唯一的淨變化是i的值。

0

由於整數下溢,while循環是否包含未定義的行爲?

首先這是一個Boolean Conversion

積分,浮點,無作用域枚舉,指針,指針到成員類型的prvalue可以轉換爲bool類型的prvalue。

值爲零(用於積分,浮點和非範圍枚舉)以及空指針和空指針至成員值變爲false。所有其他值變爲true

所以不會有整數下溢如果i正確初始化,將達到0U和將被強制轉換爲false值。

那麼編譯器有效地在這裏做的是:while(static_cast<bool>(i--))

難道編譯器都正確的假設,while循環總是true因爲這一點,帶着無盡的循環結束了?

這不是無限循環的關鍵原因是postfix decrement operator返回減量變量的值。它的定義爲T operator--(T&, int)正如之前討論的那樣,編譯器將評估該值是否爲0U,作爲轉換爲bool的一部分。

什麼編譯器有效地在這裏做的是:while(0 != operator--(i, 1))

在你更新你舉這個代碼作爲你的問題的一個動機:

void fn(void) 
{ 
    /* write something after this comment so that the program output is 10 */ 
    int a[1] = {0}; 
    int j = 0; 
    while(a[j] != 5) ++j; /* Search stack until you find 5 */ 
    a[j] = 10;    /* Overwrite it with 10 */ 
    /* write something before this comment */ 
} 

經檢查,這整個程序已經未定義的行爲有隻有a的1個元素,並且它已初始化爲0.因此,對於除0以外的任何索引,a[j]都在關注數組的末尾。這將持續到找到5,或者直到操作系統錯誤,因爲程序已從受保護的內存中讀取。這與您的循環條件不同,後綴遞減運算符返回值爲0時將退出,因此不能假定這始終爲true,或者循環將無限持續。

如果isigned int

上述兩種轉換均內置於C++。它們都是爲所有整數類型定義的,有符號和無符號。所以最終這擴展到:

while(static_cast<bool>(operator--(i, 1))) 

它是否包含與數組訪問有關的陷阱?

同樣,這調用內置的操作中,subscript operatorT& operator[](T*, std::ptrdiff_t)

所以a[i]是調用operator(a, static_cast<ptrdiff_t>(i))

相當於所以顯而易見的後續問題是什麼是ptrdiff_t?它是一個實現定義的整數,但是因爲標準的每個實現都負責定義這種類型的轉換,所以i將正確轉換。

+0

運算符返回引用與什麼有關?那麼'operator ++'如何進入呢?你的困惑。 –

+0

@LightnessRacesinOrbit謝謝。在'operator ++'鍵入錯誤的東西。但是,你的意思是什麼?「操作符 - 返回一個引用與什麼有什麼關係?」如果這不能返回某些「時間」條件沒有任何評估的東西, –

+1

它可以通過價值回報,而事實上它的確如此。後綴減少_has to_(對於內置的算術類型,前綴減量無論如何,根據您鏈接到的引用)。此外,很顯然,'我 - '評估的東西,否則代碼將無法編譯。我認爲整個部分對於這個問題來說是一條紅色的鯡魚。 –

1

您的代碼表現良好,並且包含N個≥的任何值沒有未定義的行爲0

讓我們集中討論的while循環中,並跟蹤通過的執行例子與N = 2

// Initialization 
#define N 2U 
unsigned int i = N; // i = 2 

// First iteration 
while(i--) // condition = i = 2 = true; i post-decremented to 1 
    s += a[i]; // i = 1 is in bounds 

// Second iteration 
while(i--) // condition = i = 1 = true; i post-decremented to 0 
    s += a[i]; // i = 0 is in bounds 

// Third iteration 
while(i--) // condition = i = 0 = false; i post-decremented to 0xFFFFFFFF 
// Loop terminated 

我們可以從這個跟蹤看出a[i]始終處於界限,i從0遞減時,這是非常明確的經歷的簽名環繞。

要回答你的第二個問題,如果我們改變了i類型signed int,改變在示例跟蹤的唯一行爲是循環終止在同一個地方,但i被減少到-1,這也是非常清楚-defined。

因此總而言之,假設N≥0,無論iunsigned int還是signed int,您的代碼都表現良好。 (如果N是負的,並且isigned int,則循環將保持遞減,直到在INT_MIN處發生未定義的行爲。)

+0

非常明確的解釋,謝謝! –

相關問題