gpt4 book ai didi

c - 编译器对 setjmp/longjmp 的特殊处理

转载 作者:行者123 更新时间:2023-12-05 00:51:12 26 4
gpt4 key购买 nike

Why volatile works for setjmp/longjmp , 用户 greggo评论:

Actually modern C compilers do need to know that setjmp is a specialcase, since there are, in general, optimizations where the change offlow caused by setjmp could badly corrupt things, and these need to beavoided. Back in K&R days, setjmp did not need special handling, anddidn't get any, and so the caveat about locals applied. Since thatcaveat is already there and (should be!) understood - and of course,setjmp use is pretty rare - there is no incentive for modern compilersto go to any extra lengths to fix the 'clobber' issue -- it wouldstill be in the language.

是否有任何详细说明这一点的引用资料,如果这是真的,是否可以安全地存在(行为不会比标准 setjmp/longjmp 更容易出错)setjmp/longjmp 的定制实现(例如,也许我想要保存一些命名不同的额外(线程本地)上下文?有没有办法告诉编译器“这个函数实际上是 setjmp/longjmp”?

最佳答案

C 语言将 setjmp 定义为一个宏,并对它可能出现的上下文施加了严格的限制,而不会调用未定义的行为。它不是一个正常的函数:你不能获取它的地址并期望通过结果指针的调用表现为正确的 setjmp 调用。

特别是,setjmp 调用的汇编代码通常不遵循与普通函数相同的调用约定。 Linux 和 Solaris 上的 SPARC 提供了一个反例:它的 setjmp 不会恢复所有调用保留的寄存器(vfork 也不会)。直到 2018 年(gcc-patches threadbugzilla entry),GCC 才感到意外。

但即使考虑到 setjmp 入口点遵循通常约定的“编译器友好”平台,仍然有必要将其识别为“返回两次”的函数。 GCC 通过名称识别类似 setjmp 的函数(包括 vfork),并提供 __attribute__((returns_twice)) 用于在自定义代码中注释此类函数。

这样做的原因是 longjmp'ing back to setjmp 可以将控制从某个变量或临时出现死的点(并且编译器将其存储重新用于不相关的东西)转移回它原来的位置(但它的存储是“被砸了”,哎呀)。

构建一个演示如何发生这种情况的示例有点棘手:被破坏的存储不能是寄存器,因为如果它被调用破坏,它不会在 setjmp 点被使用,如果它是调用保存的longjmp 将恢复它(除了 SPARC 异常(exception))。因此,它需要强制堆栈,而不会使两个变量的地址以使它们的生命周期重叠的方式暴露,防止堆栈槽的重用,并且不让其中一个在 longjmp 之前超出范围。

幸运的是,我设法得到了以下测试用例,当使用 -O2 -mtune-ctrl=^inter_unit_moves_from_vec (view on Compiler Explorer) 编译时:

//__attribute__((returns_twice))
int my_setjmp(void);

__attribute__((noreturn))
void my_longjmp(int);

static inline
int float_as_int(float x)
{
return (union{float f; int i;}){x}.i;
}

float f(void);

int g(void)
{
int ret = float_as_int(f());

if (__builtin_expect(my_setjmp(), 1)) {
int tmp = float_as_int(f());
my_longjmp(tmp);
}
return ret;
}

产生以下程序集:

g:
sub rsp, 24
call f
movss DWORD PTR [rsp+12], xmm0
call my_setjmp
test eax, eax
je .L2
call f
movss DWORD PTR [rsp+12], xmm0
mov edi, DWORD PTR [rsp+12]
call my_longjmp
.L2:
mov eax, DWORD PTR [rsp+12]
add rsp, 24
ret

-mtune-ctrl=^inter_unit_moves_from_vec 标志导致 GCC 通过堆栈实现 SSE-to-gpr 移动,并且两个移动使用相同的堆栈槽,因为据编译器所知,没有冲突(计算 'tmp' 会导致 noreturn 函数,因此不再需要临时用于计算 'ret')。但是,如果 my_longjmp 将控制权转移回 my_setjmp,则在分支到标签 .L2 之后,我们会尝试从被覆盖的插槽中读取 'ret' 的值。

关于c - 编译器对 setjmp/longjmp 的特殊处理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72711501/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com