2

如果一个人最终陷入使用 setjmp/longjmp (don't ask)卡住的情况,那么编译器会发出很多很好的警告,说明你什么时候做错了。

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

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()框架,它会说错误已“优化”。

这很令人费解,因为我正在编译-O0 所以“优化出来”的说法很奇怪。但是与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在我看到的任何示例中 s 都不是本地人。那是你不能做的事情吗?:-/


注意:有关构造的“语言律师”问题,请参阅下面的@AnT 的回答和评论setjmp() ? ... : ...。但我实际上在这里发生的事情原来是函数退出后的一个损坏的 longjmp 调用。根据longjmp()文档(也:常识),这绝对是错误的;我只是没有意识到这就是发生的事情:

如果调用 setjmp 的函数已退出,则行为未定义(换句话说,只允许长跳转调用堆栈)

4

1 回答 1

3

是否有理由将helper调用“嵌入”到ifthrough?:运算符的控制表达式中?这实际上违反了说的语言要求

7.13.1.1 setjmp 宏

4 setjmp 宏的调用应仅出现在以下上下文之一中:

— 选择或迭代语句的整个控制表达式;

— 关系或相等运算符的一个操作数与另一个操作数为整数常量表达式,结果表达式是选择或迭代语句的整个控制表达式;

— 一元的操作数!运算符,其结果表达式是选择或迭代语句的整个控制表达式;或者

— 表达式语句的整个表达式(可能强制转换为 void)。

5如果调用出现在任何其他上下文中,则行为未定义。

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

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

在您的情况下,您不应该尝试通过制作 variables 来解决问题volatile,而应该简化setjmp用于符合上述要求的上下文。类似的东西

if (setjmp(buf) != 0) {
  helper(&error);
  ...
  return;
}
于 2015-05-23T19:16:14.890 回答