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