未定义的行为或未定义的行为

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)

一些警告:

  1. 你不应该写这样的代码.这绝对是错误的编码风格,永远不应该进入一个严肃的应用程序.纯娱乐.

一些解释:

  1. 我寻找未定义的行为,因为A在其初始化中使用了值.再说一遍:你不应该这样做.
  2. 然而,构建表达式的方式,代码的两个部分将作为编译器产生0

所以我现在处于这种困境,无论是UB还是UB.

Ant*_*ala 7

首先,如果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的立场.
  • 委员会同意,该领域将受益于类似于"摇摆"值的某种新定义,并且应在本标准的任何后续修订中予以考虑.
  • 该委员会还指出,结构内的填充字节可能是"摇摆"表示的一种不同形式.