给出此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标准表示不应。
答案 0 :(得分:2)
setjmp
是一个宏,而不是一个函数,这是标准的一种认识,即在某些实现中它可能需要正常功能不可用的功能。
对于可以使用标准调用语义的函数实现的实现,该标准明确允许宏简单地扩展为同名的函数。但是,如果应用程序尝试使用#undef或使用(setjmp)(jmpbuf)
绕过宏,则会产生未定义的行为。这与普通标准库函数相反,普通标准库函数也可以实现为宏以及函数,但是可以使用上述技术进行访问以避免宏扩展。
此外,setjmp
被指定为宏的事实意味着&setbuf
也是未定义的行为。实际上,该标准仅允许在以下两种情况下调用setbuf
:
作为完整的表达式语句,可能带有显式强制转换为void
在if
或循环语句的条件下,且仅在条件为
setjmp
本身
以!
调用为参数的运算符setjmp
setjmp
调用和整数常量之间的比较。
换句话说,对setjmp
的调用的值不能保存或参与算术,并且不能在围绕调用上下文的序列点内执行其他任何计算。
因此,标准为实现setjmp
提供了很大的自由度。
答案 1 :(得分:0)
当涉及setjmp时,为什么此编译器输出错误?
因为编译器不符合标准。因为执行生成的机器代码时产生的副作用与C标准中规定的抽象机器的规范不符。您的代码很简单,因为label_0 1 2
label_1
NaN NaN NaN
a 0.0 NaN
b 2.0 5.0
c 4.0 8.0
会产生副作用。编译器(仅)的工作通常是将句子从一种语言翻译成另一种语言,以使副作用保持不变。如果做不到这一点,那就坏了。
从调用longjmp函数时起,所有可访问的对象都具有值,并且抽象机的所有其他组件都具有状态,只是自动存储持续时间的对象的值在包含调用以下内容的函数中是局部的不确定的,不具有volatile限定类型且已在setjmp调用和longjmp调用之间更改的相应setjmp宏是不确定的。
printf
具有自动存储时间,并且是函数中的局部变量,并且不可变,但是在调用x
和setjmp
之间未进行修改。因此,如果longjmp
在调用x
之前具有某个值,那么它在调用longjmp
之后必须具有相同的值。
生成不符合该规范的汇编代码的编译器是不合格的C编译器,或者更严格地说,它根本不是C编译器。
有些编译器和体系结构不支持longjmp
/ setjmp
调用,很好。此类编译器明确声明它们不支持longjmp
,例如,它们可能缺少setjmp
标头,因此无法使用它。