c - 为什么涉及setjmp时编译器输出错误?

标签 c compiler-construction

给出这个 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:

  1. 作为完整的表达式语句,可能显式转换为 void

  2. if 或循环语句的条件下,且仅当条件为

    • setjmp 调用本身

    • setjmp 调用作为参数的运算符 !

    • setjmp 调用与整数常量之间的比较。

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

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

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

相关文章:

Ubuntu 中的 C 和 SDL 未按预期工作

objective-c - 快速找到数字的下一个倍数的方法

java - 中间节点作为支配树的根节点

compiler-construction - Flex编译错误

Java 5.0 编译器 API?

c - 当正交设置为 -1.0 到 1.0 时获取鼠标位置

c - 当 gets(t) 从命令行获取输入 "Q"时,为什么 t != "Q"不终止 while 循环?

c - 在 C 中将多维数组复制到另一个时出现语法错误

c++ - C++ 的热重编译

multithreading - 使用多核提高并行性能