鉴于此C代码

#include <stdio.h>
#include <setjmp.h>
void foo(int x) {
  jmp_buf env;
  if (setjmp(env) == 0) {
    printf("%d\n", 23);
    longjmp(env, 1);
  } else {
    printf("%d\n", x);
  }
}


结果应该是先打印23,再打印x,并且都应该定义清楚。

但是可以说编译器不知道setjmp / longjmp是特殊功能,它生成以下代码:

;function foo
;r0 : int x

foo:
    sub sp, sp, #sizeof(jmp_buf) ; reserve space for env
    push r0         ; save x for later
    add r0, sp, #4  ; load address of env
    call setjmp
    pop r1          ; restore SP, move x to r1 <<== corrupt after jongjmp
    cmp r0, #0      ; if (setjmp(env) == 0)
    bne 1f
    lea r0, "%d\n"  ; printf("%d\n", 23)
    mov r1, #23
    call printf
    mov r0, sp  ; load address of env
    mov r1, #1
    call longjmp
    b 2f
1:
    lea r0, "%d\n"      ; printf("%\dn", x), x already in r1
    call printf
2:
    add sp, sizeof(jmp_buf)
    ret


这将按预期方式打印23,但随后将打印longjmp调用的重运行地址,即1标签的地址。

变量x只是临时存储在堆栈上,以在setjmp函数调用中保留它(r0是自变量寄存器,保存了调用者)。我认为对于编译器而言,这是完全正确的事情。但是由于setjmp返回两次,因此破坏了变量,而C标准表示不应这样做。

最佳答案

setjmp是一个宏,而不是一个函数,这是标准的一种认识,即在某些实现中,它可能需要正常功能不可用的功能。

对于可以使用标准调用语义的函数实现的实现,该标准明确允许宏简单地扩展为同名的函数。但是,如果应用程序尝试使用#undef或使用(setjmp)(jmpbuf)绕过宏,则会产生未定义的行为。这与普通标准库函数相反,普通标准库函数也可以实现为宏以及函数,但是可以使用上述技术进行访问以避免宏扩展。

同样,将setjmp指定为宏的事实意味着&setbuf也是未定义的行为。实际上,该标准仅允许在两个上下文中调用setbuf


作为完整的表达式语句,可能带有显式强制转换为void
if或循环语句的条件下,且仅当条件为


setjmp调用本身
!调用为参数的运算符setjmp
setjmp调用与整数常量之间的比较。



换句话说,对setjmp的调用的值无法保存或参与算术运算,并且无法在围绕调用上下文的序列点内执行其他计算。

因此,该标准为实现setjmp提供了很大的自由度。

关于c - 涉及setjmp时,为什么此编译器输出错误?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56656914/

10-13 07:24