Fer*_*eak 2 c undefined-behavior language-lawyer
考虑以下代码:
#include <stdio.h>
int main()
{
char A = A ? 0[&A] & !A : A^A;
putchar(A);
}
Run Code Online (Sandbox Code Playgroud)
我想问一下,是否观察到任何未定义的行为.
编辑
请注意:代码故意使用0[&A] & !A和NOT 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
Run Code Online (Sandbox Code Playgroud)
然而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
Run Code Online (Sandbox Code Playgroud)
而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
Run Code Online (Sandbox Code Playgroud)
但通过优化,我们可以获得更清晰的代码(gcc和clang):
main: # @main
push rax
mov rsi, qword ptr [rip + stdout]
xor edi, edi
call _IO_putc
xor eax, eax
pop rcx
ret
Run Code Online (Sandbox Code Playgroud)
还有一种神秘的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
Run Code Online (Sandbox Code Playgroud)
一些警告:
一些解释:
A在其初始化中使用了值.再说一遍:你不应该这样做.所以我现在处于这种困境,无论是UB还是UB.
首先,如果char对应unsigned char,char则不能有陷阱表示; 但是如果char对应signed char它可以有陷阱表示.由于使用陷阱表示具有未定义的行为,因此修改要使用的代码会更有趣unsigned char:
unsigned char A = A ? 0[&A] & !A : A^A;
putchar(A);
Run Code Online (Sandbox Code Playgroud)
最初我认为在C中没有任何未定义的行为.问题是以A未定义行为的方式未初始化,答案是"否",因为尽管它是具有自动存储持续时间的局部变量,但它具有它的地址被占用,所以它必须驻留在内存中,并且它的类型是char,因此它的值是未指定的,但具体来说它不能是陷阱表示.
C11附录J.2.指定以下内容具有未定义的行为:
指定可以使用寄存器存储类声明的自动存储持续时间的对象的左值在需要指定对象的值的上下文中使用,但该对象未初始化.(6.3.2.1).
与6.3.2.1p2说的那样
如果左值指定了一个自动存储持续时间的对象,该对象可以使用寄存器存储类声明(从未使用其地址),并且该对象未初始化(未使用初始化程序声明,并且在使用之前未对其进行任何赋值) ),行为未定义.
由于采用了地址A,因此无法使用register存储类声明它,因此根据此6.3.2.1p2,它的使用没有未定义的行为; 相反,它会有一个未指定但有效的char值; chars没有陷阱表示.
但是,问题是没有任何要求A必须产生相同的未指定值,因为未指定的值是
相关类型的有效值,其中本国际标准不对任何情况下选择的值施加任何要求
而C11 缺陷报告451的答案似乎认为这具有未定义的行为,并说unsigned char在算术表达式中使用不确定值(即使是没有陷阱表示的类型)的结果也意味着结果将具有不稳定的值,并且在库函数中使用此类值将具有未定义的行为.
从而:
unsigned char A = A ? 0[&A] & !A : A^A;
Run Code Online (Sandbox Code Playgroud)
不会像这样调用未定义的行为,但A仍然使用不确定的值进行初始化,并且在调用库函数时使用这样的不确定值putchar(A)应被视为具有未定义的行为:
拟议的委员会答复
- 问题1的答案是"是",在所述条件下的未初始化值似乎可以改变其价值.
- 问题2的答案是,对不确定值执行的任何操作都将具有不确定的值.
- 问题3的答案是,当用于不确定的值时,库函数将表现出未定义的行为.
- 这些答案适用于没有陷阱表示的所有类型.
- 这一观点再次肯定了C99 DR260的立场.
- 委员会同意,该领域将受益于类似于"摇摆"值的某种新定义,并且应在本标准的任何后续修订中予以考虑.
- 该委员会还指出,结构内的填充字节可能是"摇摆"表示的一种不同形式.