22

调用 longjmp() 后,如果非 volatile 限定的本地对象的值在调用 setjmp() 后可能已更改,则不应访问它们。在这种情况下,它们的值被认为是不确定的,访问它们是未定义的行为。

现在我的问题是为什么 volatile 在这种情况下有效?那个 volatile 变量的变化不会仍然使 longjmp 失败吗?例如,在下面给出的示例中,longjmp 将如何正常工作?当代码在 longjmp 之后返回到 setjmp 时,local_var 的值不会是 2 而不是 1 吗?

void some_function()
{
  volatile int local_var = 1;

  setjmp( buf );
  local_var = 2;
  longjmp( buf, 1 );
}
4

3 回答 3

23

setjmplongjmpclobber寄存器。如果一个变量存储在寄存器中,它的值在longjmp.

相反,如果它被声明为volatile,那么每次写入时,它都会存储回内存,每次读取时,都会从内存中读回。这会损害性能,因为编译器必须进行更多的内存访问而不是使用寄存器,但它使变量的使用在面对longjmping 时是安全的。

于 2011-11-03T14:52:53.693 回答
12

在这种情况下,关键在于优化:优化器自然会期望调用像 setjmp() 这样的函数不会更改任何局部变量,并优化对变量的读取访问。例子:

int foo;
foo = 5;
if ( setjmp(buf) != 2 ) {
   if ( foo != 5 ) { optimize_me(); longjmp(buf, 2); }
   foo = 6;
   longjmp( buf, 1 );
   return 1;
}
return 0;

优化器可以优化掉 optimize_me 行,因为 foo 已在第 2 行中写入,不需要在第 4 行中读取并且可以假定为 5。此外,可以删除第 5 行中的赋值,因为 foo 永远不会被读取再次,如果 longjmp 是一个正常的 C 函数。但是,setjmp() 和 longjmp() 以优化器无法解释的方式干扰了代码流,从而破坏了这个方案。此代码的正确结果是终止;随着线的优化,我们有一个无限循环。

于 2011-11-03T14:51:18.853 回答
8

缺少 'volatile' 限定符出现问题的最常见原因是编译器通常会将局部变量放入寄存器中。这些寄存器几乎肯定会用于 setjmp 和 longjmp 之间的其他事情。确保将这些寄存器用于其他目的不会导致变量在 longjmp 之后保存错误值的最实用方法是将这些寄存器的值缓存在 jmp_buf 中。这可行,但有副作用,编译器无法更新 jmp_buf 的内容以反映寄存器缓存后对变量所做的更改。

如果这是唯一的问题,那么访问未声明为 volatile 的局部变量的结果将是不确定的,但不是未定义的行为。但是,即使是内存变量也存在问题,这是 thiton 所暗示的:即使一个局部变量恰好被分配在堆栈上,编译器也可以在任何时候确定它的值不再存在时用其他东西覆盖该变量需要。例如,当例程调用其他例程时,编译器可以识别出某些变量永远不会“活跃”,将这些变量放在其堆栈框架中的最浅层,并在调用其他例程之前将它们弹出。在这种情况下,即使调用 setjmp() 时变量存在于内存中,该内存也可能已被重用于其他用途,例如保存返回地址。因此,在执行 longjmp() 之后,

在变量定义中添加 'volatile' 限定符会导致专门为使用该变量保留存储空间,只要它在范围内。无论 setjmp 和 longjmp 之间发生什么,只要控制没有离开声明变量的范围,则不允许将该位置用于任何其他目的。

于 2011-11-03T15:01:12.927 回答