簡體   English   中英

未定義的行為或未定義的行為

[英]Undefined behaviour or not undefined behaviour

考慮以下代碼:

#include <stdio.h>

int main()
{
    char A = A ? 0[&A] & !A : A^A;
    putchar(A);
}

我想問一下,是否觀察到任何未定義的行為。

編輯

請注意:代碼故意使用0[&A] & !A而非A & !A (請參閱下面的回復)

結束編輯

從g ++ 6.3( https://godbolt.org/g/4db6uO )獲取輸出ASM得到(沒有使用優化):

main:
    push    rbp
    mov     rbp, rsp
    sub     rsp, 16
    mov     BYTE PTR [rbp-1], 0
    movzx   eax, BYTE PTR [rbp-1]
    movsx   eax, al
    mov     edi, eax
    call    putchar
    mov     eax, 0
    leave
    ret

然而clang為同一件事提供了更多的代碼(沒有再次優化):

main:                                   # @main
    push    rbp
    mov     rbp, rsp
    sub     rsp, 16
    mov     dword ptr [rbp - 4], 0
    cmp     byte ptr [rbp - 5], 0
    je      .LBB0_2
    movsx   eax, byte ptr [rbp - 5]
    cmp     byte ptr [rbp - 5], 0
    setne   cl
    xor     cl, -1
    and     cl, 1
    movzx   edx, cl
    and     eax, edx
    mov     dword ptr [rbp - 12], eax # 4-byte Spill
    jmp     .LBB0_3
.LBB0_2:
    movsx   eax, byte ptr [rbp - 5]
    movsx   ecx, byte ptr [rbp - 5]
    xor     eax, ecx
    mov     dword ptr [rbp - 12], eax # 4-byte Spill
.LBB0_3:
    mov     eax, dword ptr [rbp - 12] # 4-byte Reload
    mov     cl, al
    mov     byte ptr [rbp - 5], cl
    movsx   edi, byte ptr [rbp - 5]
    call    putchar
    mov     edi, dword ptr [rbp - 4]
    mov     dword ptr [rbp - 16], eax # 4-byte Spill
    mov     eax, edi
    add     rsp, 16
    pop     rbp
    ret

而Microsoft VC編譯器給出:

EXTRN   _putchar:PROC
tv76 = -12                                          ; size = 4
tv69 = -8                                         ; size = 4
_A$ = -1                                                ; size = 1
_main   PROC
    push     ebp
    mov      ebp, esp
    sub      esp, 12              ; 0000000cH
    movsx    eax, BYTE PTR _A$[ebp]
    test     eax, eax
    je       SHORT $LN5@main
    movsx    ecx, BYTE PTR _A$[ebp]
    test     ecx, ecx
    jne      SHORT $LN3@main
    mov      DWORD PTR tv69[ebp], 1
    jmp      SHORT $LN4@main
$LN3@main:
    mov      DWORD PTR tv69[ebp], 0
$LN4@main:
    mov      edx, 1
    imul     eax, edx, 0
    movsx    ecx, BYTE PTR _A$[ebp+eax]
    and      ecx, DWORD PTR tv69[ebp]
    mov      DWORD PTR tv76[ebp], ecx
    jmp      SHORT $LN6@main
$LN5@main:
    movsx    edx, BYTE PTR _A$[ebp]
    movsx    eax, BYTE PTR _A$[ebp]
    xor      edx, eax
    mov      DWORD PTR tv76[ebp], edx
$LN6@main:
    mov      cl, BYTE PTR tv76[ebp]
    mov      BYTE PTR _A$[ebp], cl
    movsx    edx, BYTE PTR _A$[ebp]
    push     edx
    call     _putchar
    add      esp, 4
    xor      eax, eax
    mov      esp, ebp
    pop      ebp
    ret      0
_main   ENDP

但通過優化,我們可以獲得更清晰的代碼(gcc和clang):

main:                                   # @main
    push    rax
    mov     rsi, qword ptr [rip + stdout]
    xor     edi, edi
    call    _IO_putc
    xor     eax, eax
    pop     rcx
    ret

還有一種神秘的VC代碼(似乎VC編譯器無法理解一個笑話......它只是不會預先計算右側)。

EXTRN   _putchar:PROC
_A$ = -1                                                ; size = 1
_main   PROC                                      ; COMDAT
    push     ecx
    mov      cl, BYTE PTR _A$[esp+4]
    test     cl, cl
    je       SHORT $LN3@main
    mov      al, cl
    xor      al, 1
    and      cl, al
    jmp      SHORT $LN4@main
$LN3@main:
    xor      cl, cl
$LN4@main:
    movsx    eax, cl
    push     eax
    call     _putchar
    xor      eax, eax
    pop      ecx
    pop      ecx
    ret      0
_main   ENDP

一些警告:

  1. 你不應該寫這樣的代碼。 這絕對是錯誤的編碼風格,永遠不應該進入一個嚴肅的應用程序。 純娛樂。

一些解釋:

  1. 我尋找未定義的行為,因為在初始化中使用了A的值。 再說一遍:你不應該這樣做。
  2. 然而,構建表達式的方式,代碼的兩個部分將作為編譯器產生0

所以我現在處於這種困境中,無論是UB還是UB。

首先,如果char對應unsigned char ,則char不能有陷阱表示; 但是如果char對應於signed char它可以有陷阱表示 由於使用陷阱表示具有未定義的行為,因此修改代碼以使用unsigned char更有趣:

unsigned char A = A ? 0[&A] & !A : A^A;
putchar(A);

最初我認為在C中沒有任何未定義的行為。問題是A未初始化的行為有未定義的行為,答案是“否”,因為盡管它是具有自動存儲持續時間的局部變量,有地址,所以它必須駐留在內存中,它的類型是char,因此它的值是未指定的,但具體來說它不能是陷阱表示。

C11附錄J.2。 指定以下內容具有未定義的行為:

指定可以使用寄存器存儲類聲明的自動存儲持續時間的對象的左值在需要指定對象的值的上下文中使用,但該對象未初始化。 (6.3.2.1)。

與6.3.2.1p2說的那樣

如果左值指定了一個自動存儲持續時間的對象,該對象可以使用寄存器存儲類聲明(從未使用其地址),並且該對象未初始化(未使用初始化程序聲明,並且在使用之前未對其進行任何賦值) ),行為未定義。

由於采用了A的地址,因此無法使用register存儲類聲明它,因此根據此6.3.2.1p2,它的使用沒有未定義的行為。 相反,它會有一個未指定但有效的char值; char s沒有陷阱表示。

但問題是,沒有任何要求A必須全部產生相同的未指定值,因為未指定的值是

相關類型的有效值,其中本國際標准不對任何情況下選擇的值施加任何要求

並且C11 缺陷報告451的答案似乎認為這具有未定義的行為 ,並且說在算術表達式中使用不確定值(即使是沒有陷阱表示的類型,例如unsigned char )的結果也意味着結果將具有不穩定的值,並且在庫函數使用此類值將具有未定義的行為

從而:

unsigned char A = A ? 0[&A] & !A : A^A;

不會像這樣調用未定義的行為,但仍然使用不確定的值初始化A ,並且在調用庫函數putchar(A)使用這樣的不確定值應該被視為具有未定義的行為:

擬議的委員會答復

  • 問題1的答案是“是”,在所述條件下的未初始化值似乎可以改變其價值。
  • 問題2的答案是,對不確定值執行的任何操作都將具有不確定的值。
  • 問題3的答案是,當用於不確定的值時,庫函數將表現出未定義的行為。
  • 這些答案適用於沒有陷阱表示的所有類型。
  • 這一觀點再次肯定了C99 DR260的立場。
  • 委員會同意這一領域將受益於類似於“搖擺”價值的新定義,並且應在本標准的任何后續修訂中予以考慮。
  • 該委員會還指出,結構內的填充字節可能是“搖擺”表示的一種不同形式。

這是一種行為類別,其中標准強烈暗示行為,標准中沒有任何內容會邀請實現跳轉軌道,但官方的“解釋”仍然允許編譯器以任意方式行事。 因此,將行為描述為“未定義”是不准確的[因為標准的文本確實暗示了一種行為,並且沒有任何暗示它不應該適用]也不准確簡單地說它是“定義的” “[因為委員會說編制者可能會以任意方式行事]。 相反,有必要識別中間條件。

因為不同的應用領域(數字運算,系統編程等)受益於不同類型的行為保證,並且因為某些平台可能比其他平台更便宜地維護某些保證,所以迄今為止每個C標准的作者一般都尋求避免對各種擔保的相對成本和收益作出判斷。 相反,他們已經顯示出對實施者在什么實施中應該提供什么保證的判斷的顯着尊重。

如果提供一些特定的行為保證在某些應用領域沒有任何價值(即使它在其他應用領域可能至關重要)似乎是合理的,並且放棄該保證有時可能允許某些實施更有效(即使在大多數情況下它也不會()),標准的作者通常不要求保證。 相反,它們讓實現者根據實現的目標平台和預期的應用程序字段決定實現是否應始終支持該保證,從不支持該保證,或允許程序員選擇(通過命令行選項或其他是否支持擔保。

用於任何特定目的的質量實現(例如系統編程)將支持使編譯器適合於該目的的行為保證(例如,讀取程序所擁有的無符號字符將不會產生任何影響,除非產生可能無意義的影響價值),標准是否要求它這樣做。 C標准的作者並不要求也不打算所有實現都適用於系統編程等領域,因此不要求針對數字運算等其他領域的實現支持這種保證。 針對其他領域的編譯器可能無法維護系統編程所需的各種保證,這意味着系統程序員確保使用適合其目的的工具非常重要。 知道一個工具有望支持所需要的保證遠比知道當前對標准的解釋支持這樣的保證更重要,因為如果編譯器作者可以建議放棄它有時候今天被認為是明確無誤的保證可能會消失是有益的。

右側首先評估A

在C ++中,由於A未初始化,因此代碼會導致未定義的行為

在C11中,由於A未初始化,因此其值可能是陷阱表示,因此代碼會導致未定義的行為。

在C11中,如果我們在一個已知沒有陷阱表示的系統上(或者我們將char更改為unsigned char ),則A具有不確定的值,然后putchar(A)通過將不確定的值傳遞給庫來導致未定義的行為功能。

進一步閱讀C11未初始化的變量使用

暫無
暫無

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

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