鉴于此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/