2014-04-01 156 views
2

變化簡單的C代碼:不同的結果

#include <stdio.h> 

int main() { 
    int arr[] = {1, 2, 3, 4, 5}; 
    int *ptr = arr; 
    printf("%d, %d\n", *ptr, *(++ptr)); 

    return 0; 
} 

編譯用gcc 4.8.2,結果是:

2, 2 

編譯鐺3.4,結果是:

1, 2 

爲什麼會發生這種情況?

+3

序列點和未定義的行爲? – odedsh

+6

這是未定義的行爲,看到這個[所以關於序列點的問題](http://stackoverflow.com/questions/4176328/undefined-behavior-and-sequence-points) –

+3

因爲'* ptr,*(++ ptr)''讓你的程序在火上點燃頭髮。 – DevSolar

回答

7

調用函數時使用的逗號不包含sequence point

因此,該代碼*ptr, *(++ptr)調用未定義行爲,因爲它試圖訪問兩次PTR序列點之間,用於其他目的,而不是確定要分配什麼值PTR。

這是通過C11 6.5/2中定義,在下面的亂碼文本:

如果一個標量對象上的副作用是相對於任一 同一標物體上的不同的副作用或一個未測序值 使用相同標量對象的值計算,行爲是未定義的 。如果子表達式的子表達式有多個允許的排序順序,那麼如果在任何排序中出現這種不確定的副作用,則行爲是不確定的。

在我們的情況下,副作用(在*(++ptr)改變PTR與++)相對於相同的對象(*ptr)的值計算是未測序。

由於它是未定義的行爲,任何事情都可能發生。既然你的程序做了「某事」,它的行爲就像預期的一樣(或者說,就像「意外的」)。

此外,函數參數的評估順序未指定,因此無法知道函數調用中的第一個或第二個參數是先評估的。順序不僅可以在編譯器之間不同,而且可以在同一程序中的源代碼行之間有所不同。編譯器可以按照它喜歡的任何順序對它們進行評估,並且它不需要記錄如何。

+0

+1用於調用C標準的「亂碼文本」。這是真的。 –

+0

@mic_e C99中的文本是完全正確的:''在前一個和下一個序列點之間,對象的存儲值 最多隻能通過評估一個表達式進行修改。此外,先驗值應該只讀以確定要存儲的值「。在C11中,他們將其重寫爲上面那些不可讀的垃圾。 – Lundin

1

試試這個:

#include <stdio.h> 

int main() { 
    int arr[] = {1, 2, 3, 4, 5}; 
    int *ptr = arr; 
    printf("%d, %d\n", ptr[0], ptr[1]); 
    /* Or this 
    printf("%d, %d\n", ptr[1], ptr[0]); 
    whichever you meant 
    */ 
    return 0; 
} 

沒有爲函數參數的計算順序,編譯器可以選擇任何他們喜歡的定義的順序。我可以先執行*(++ptr),然後執行*ptr,這會導致將參數2, 2傳遞給printf,或者反過來導致1, 2傳遞給printf。

+3

而且clang實際上有一個'-Wunsequenced'的警告,它是'-Wall'的一部分。第零個錯誤正在發展,甚至沒有基本的警告。 –

+1

這並不能完全回答這個問題,因爲結果不僅僅依賴於評估的順序,還會調用未定義的行爲。 – Lundin

1

printf行中的評估順序未指定,單獨足以允許兩種解釋。

另外,兩個指針訪問之間沒有順序點。這會導致未定義的行爲(請參閱:您的程序可能會崩潰,按預期工作,或將硬盤內容上傳到互聯網,具體取決於當前的星期幾以及您使用的編譯器)。

如果有警告編譯,你會發現以下幾點:

[email protected] $ gcc test.c -std=c11 -Wall -Wextra -pedantic 
test.c: In function ‘main’: 
test.c:6:29: warning: operation on ‘ptr’ may be undefined [-Wsequence-point] 
    printf("%d, %d\n", *ptr, *(++ptr)); 
          ^
[email protected] $ clang test.c -std=c11 -Wall -Wextra -pedantic 
test.c:6:29: warning: unsequenced modification and access to 'ptr' [-Wunsequenced] 
     printf("%d, %d\n", *ptr, *(++ptr)); 
          ~~~ ^
1 warning generated. 

生活提示:請使用-Wall -Wextra -pedantic始終編譯,總是解決所有警告,常常覺得既clanggcc測試,你就會有很少有錯誤。我甚至將-Werror添加到我的發佈版本配置中。

+0

正如clang告訴你的,ptr有一個無法修改的修改。這又意味着程序調用未定義的行爲。所以問題不僅僅是評估的順序。 – Lundin

+0

注意自己:做一個鐺叉,將實現我在我的帖子中描述的那種未定義的行爲。 –

+0

對於編譯器fork來說,+1實際上*確實*在UB上真正的惡意。 * *最終會教給他們。雖然不是每個人都有一個快速上傳,但我會去做一些像硬盤驅動器的分區表一樣的東西,然後試圖破壞高清頭部。 ;-) – DevSolar