setjmp/longjmp 中未修改的本地地址是否会损坏?

Can addresses of unmodified locals wind up corrupted in setjmp/longjmp?

如果在使用 setjmp/longjmp (不要问) 时遇到卡住的情况,那么编译器会发出很多很好的警告,说明何时你可能做错了什么。

但是在 Clang 中使用 Address Sanitizer 构建 -Wall -Wextra -pedantic 时,我得到了一个大致平行于以下情况的案例:

void outer() {
    jmp_buf buf;
    ERR error;

    if (setjmp(buf) ? helper(&error) : FALSE) {
        // process whatever helper decided to write into error
        return;
    }

    // do the stuff you wanted to guard that may longjmp.
    // error is never modified
}

在 longjmp 上,查看 helper 堆栈帧,错误指针为空。如果我查看 outer() 框架,它说错误是 "optimized out".

这很令人费解,因为我正在使用 -O0 进行编译,所以 "optimized out" 说起来很奇怪。但是对于大多数事情 longjmp-y,我想知道是什么让编译器无法提前决定将错误地址放入哪个寄存器……然后作废。

是地址清理程序在欺骗我,还是我真的必须写这样的东西:

void outer() {
    jmp_buf buf;
    ERR error;
    volatile ERR* error_ptr = &error;

    if (setjmp(buf) ? helper(error_ptr) : FALSE) {
        // process whatever helper decided to write into error
        return;
    }

    // do the stuff you wanted to guard that may longjmp.
    // error is never modified
}

当我研究这个时,我注意到 jmp_buf 在我看到的任何例子中都不是本地人。那是你不能做的事吗? :-/


注意: 请参阅下面@AnT 的回答和评论,了解有关 setjmp() ? ... : ... 构造的 "language-lawyer" 问题。但我实际上在这里进行的是在函数退出后发生的损坏的 longjmp 调用。根据 longjmp() docs(也:常识),这 绝对 坏了;我只是没有意识到发生了什么:

If the function that called setjmp has exited, the behavior is undefined (in other words, only long jumps up the call stack are allowed)

helper 调用通过 ?: 运算符 "embedded" 进入 if 的控制表达式是否有原因?这实际上违反了

的语言要求

7.13.1.1 The setjmp macro

4 An invocation of the setjmp macro shall appear only in one of the following contexts:

— the entire controlling expression of a selection or iteration statement;

— one operand of a relational or equality operator with the other operand an integer constant expression, with the resulting expression being the entire controlling expression of a selection or iteration statement;

— the operand of a unary ! operator with the resulting expression being the entire controlling expression of a selection or iteration statement; or

— the entire expression of an expression statement (possibly cast to void).

5 If the invocation appears in any other context, the behavior is undefined.

该要求的重点是确保 setjmp 中由 longjmp 触发的 "unpredictable" return 不应位于表达式的中间评估,即在未排序的上下文中。在您的具体示例中,很明显,从抽象 C 语言的角度来看,变量 error 不可能通过 setjmp 调用更改,这为许多优化打开了大门。

很难说这里发生了什么,因为 helper 收到一个指针 &error,而不是 error 的直接值。从表面上看,从实用的角度来看,一切似乎都很好。但正式的行为是未定义的。

在您的情况下,您不应尝试通过创建变量 volatile 来解决问题,而应简化使用 setjmp 的上下文以符合上述要求。类似于

if (setjmp(buf) != 0) {
  helper(&error);
  ...
  return;
}