为什么volatile适用于setjmp / longjmp

时间:2011-11-03 14:44:40

标签: c linux gcc x86 setjmp

在调用longjmp()之后,如果自调用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 );
}

3 个答案:

答案 0 :(得分:20)

setjmplongjmp clobber寄存器。如果变量存储在寄存器中,则其值在longjmp之后会丢失。

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

答案 1 :(得分:10)

关键在于这种情况下的优化:优化器自然会期望调用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()会以优化程序无法解释的方式干扰代码流,从而破坏了此方案。这段代码的正确结果是终止;随着线路优化,我们有一个无限循环。

答案 2 :(得分:8)

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

如果这是唯一的问题,那么访问未声明为volatile的局部变量的结果将是不确定的,而不是未定义的行为。但是,即使使用内存变量也会出现问题,但是,即使在堆栈上分配了一个局部变量,编译器也可以随时用其他东西覆盖该变量,而不管它是否已经确定其值已不再存在。需要。例如,当例程调用其他例程时,编译器可以识别某些变量永远不会“活动”,将这些变量置于其堆栈帧中最浅层,并在调用其他例程之前弹出它们。在这种情况下,即使调用setjmp()时内存中存在变量,该内存也可能被重用于其他内容,例如保存返回地址。因此,在执行longjmp()之后,内存将被视为未初始化。

将“volatile”限定符添加到变量的定义会导致存储只保留 以供该变量使用,只要它在范围内即可。无论setjmp和longjmp之间发生了什么,如果控制没有离开声明变量的范围,则不允许将该位置用于任何其他目的。