[英]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
一些警告:
一些解釋:
A
的值。 再說一遍:你不應該這樣做。 所以我現在處於這種困境中,無論是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)
通過將不確定的值傳遞給庫來導致未定義的行為功能。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.