在调用longjmp()之后,如果自从调用setjmp()以来它们的值可能已经改变,那么不应该访问非volatile限定的局部对象。 他们在这种情况下的价值被认为是不确定的,访问它们是不确定的行为。
现在我的问题是为什么在这种情况下易变的作品? 在那个volatilevariables中不会改变仍然失败longjmp? 例如,在下面给出的例子中longjmp将如何正确工作? 当代码返回到longjmp之后的setjmp时,local_var的值是2而不是1吗?
void some_function() { volatile int local_var = 1; setjmp( buf ); local_var = 2; longjmp( buf, 1 ); }
setjmp
和longjmp
clobber寄存器。 如果一个变量存储在一个寄存器中,那么在longjmp
之后它的值会丢失。
相反,如果它被声明为volatile
,那么每次被写入时,它都被存储回内存,每次读取它时,都会从内存中读回。 这会损害性能,因为编译器不得不进行更多的内存访问,而不是使用寄存器,但是这会使得变量在longjmp
的使用安全。
关键是在这种情况下进行优化:优化器自然期望调用像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()会以优化程序无法解释的方式打乱代码流,从而打破此方案。 此代码的正确结果将是终止; 随着线路优化,我们有一个无限循环。
在不存在“易失性”限定符的情况下,最常见的原因是编译器通常会将局部变量放入寄存器中。 这些寄存器几乎肯定会用于setjmp和longjmp之间的其他事物。 longjmp在jmp_buf中缓存这些寄存器的值之后,确保将这些寄存器用于其他目的的最实际的方法不会导致变量保存错误的值。 这是有效的,但有副作用,编译器没有办法更新jmp_buf的内容,以反映缓存寄存器后对变量所做的更改。
如果这是唯一的问题,访问本地变量的结果不声明易失性将是不确定的,但不是未定义的行为。 即使存储器变量存在一个问题,尽管thiton暗示:即使一个局部变量恰好在堆栈上分配,编译器可以自由地用其他东西覆盖该变量,只要它确定它的值不再需要。 例如,一个编译器可以识别当一个例程调用其他例程时,某些变量永远不会“活动”,将这些变量放在其最底层的栈帧中,并在调用其他例程之前弹出它们。 在这种情况下,即使调用setjmp()时变量存在于内存中,该内存可能已被重用于其他类似于保存返回地址的内容。 因此,执行longjmp()之后,内存将被视为未初始化。
将“volatile”限定符添加到变量的定义中会使存储专门用于该变量,只要它在范围内即可。 无论setjmp和longjmp之间发生了什么,提供的控件都没有离开变量被声明的范围,任何东西都不能用于其他目的。