为什么volatile适用于setjmp/longjmp

Met*_*est 20 c linux x86 gcc setjmp

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

现在我的问题是为什么在这种情况下挥发性工作?不会改变那个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 );
}
Run Code Online (Sandbox Code Playgroud)

Ada*_*eld 20

setjmplongjmpclobber寄存器.如果变量存储在寄存器中,则其值在a之后丢失longjmp.

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

  • 那么你可以添加一些额外的代码,只是在调用`setjmp`时使它变得不稳定.像:int x;/*做东西*/volatile int save_x = x; if(setjmp(buf)){x = save_x;/*做东西*/}`.这通过使用非易失性变量来最大化"setjmp"之前和之后的性能,但通过在调用中使用volatile变量来确保安全性. (4认同)
  • 有什么方法可以减少由于易失性而导致的性能开销,同时仍然可以准确执行?多少波动会影响优化? (2认同)

thi*_*ton 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;
Run Code Online (Sandbox Code Playgroud)

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


sup*_*cat 8

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

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

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