0 总结
- longjmp与setjmp语句之间的变量赋值会丢失。
- 变量须满足:
- 在调用setjmp函数中的局部变量(栈变量) ,全局变量不受影响
- 非volatile
解决方法:加volatile
1 问题复现
代码语言:javascript复制#include <setjmp.h>
#include <stdio.h>
int d = 4;
int e = 5;
int main()
{
int a = 1;
int b = 2;
int c = 3;
sigjmp_buf local_sigjmp_buf;
a = 10;
if (sigsetjmp(local_sigjmp_buf, 0) == 0)
{
b = 20;
d = 40;
siglongjmp(local_sigjmp_buf, 1);
}
else
{
c = 30;
e = 50;
}
printf("a = %d,b = %d,c = %d,d = %d, e = %dn", a, b, c, d, e);
return 0;
}
使用O1编译
执行结果:b=20赋值丢失
代码语言:javascript复制$ gcc -o main3 -Wall -g -ggdb -O1 -g3 -gdwarf-2 main3.c
$ ./main3
a = 10,b = 2,c = 30,d = 40, e = 50
使用O0编译
执行结果:符合预期
代码语言:javascript复制$ gcc -o main3 -Wall -g -ggdb -O0 -g3 -gdwarf-2 main3.c
$ ./main3
a = 10,b = 20,c = 30,d = 40, e = 50
2 原因与解法
编译器在O1优化下,把sigsetjmp与siglongjmp之间的局部变量赋值操作丢掉了。
对比:左侧gcc O0,右侧gcc O1
手册中已有说明,满足三个条件的变量赋值无效:
- 变量属于setjmp所在函数的局部变量:必须是栈上的变量。
- 变量在setjmp和longjmp之间有修改。
- 变量没有volatile。
LONGJMP(3) Linux Programmer's Manual LONGJMP(3)
NAME
longjmp, siglongjmp - nonlocal jump to a saved stack context
SYNOPSIS
#include <setjmp.h>
void longjmp(jmp_buf env, int val);
void siglongjmp(sigjmp_buf env, int val);
NOTES
The values of automatic variables are unspecified after a call to longjmp() if they meet all the following criteria:
· they are local to the function that made the corresponding setjmp(3) call;
· their values are changed between the calls to setjmp(3) and longjmp(); and
· they are not declared as volatile.
解法很简单:加volatile
这类一旦发生很难排查,事后排查难度远大于代码review发现。
3 对PG的影响
Postgresql中的存在大量PG_TRY/PG_CATCH宏的使用:
例如
这类宏是对sigsetjmp、siglongjmp函数的一层封装:(这里给一段PG10的定义,比较简单)
代码语言:javascript复制// 全局变量
sigjmp_buf *PG_exception_stack = NULL;
// 宏定义
#define PG_TRY()
do {
sigjmp_buf *save_exception_stack = PG_exception_stack;
ErrorContextCallback *save_context_stack = error_context_stack;
sigjmp_buf local_sigjmp_buf;
if (sigsetjmp(local_sigjmp_buf, 0) == 0)
{
PG_exception_stack = &local_sigjmp_buf
#define PG_CATCH()
}
else
{
PG_exception_stack = save_exception_stack;
error_context_stack = save_context_stack
#define PG_END_TRY()
}
PG_exception_stack = save_exception_stack;
error_context_stack = save_context_stack;
} while (0)
对于这几个宏,在使用时需要注意:
如果在PG_TRY里面修改了栈变量,一定要确认变量加了volatile,全局变量不受影响。
新版本的PG也在注释中做了提示。