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;
}
如果在使用 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;
}