解决方法:加volatile
#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 = %d\n", a, b, c, d, e);
return 0;
}
使用O1编译
执行结果:b=20赋值丢失
$ gcc -o main3 -Wall -g -ggdb -O1 -g3 -gdwarf-2 main3.c
$ ./main3
a = 10,b = 2,c = 30,d = 40, e = 50
使用O0编译
执行结果:符合预期
$ gcc -o main3 -Wall -g -ggdb -O0 -g3 -gdwarf-2 main3.c
$ ./main3
a = 10,b = 20,c = 30,d = 40, e = 50
编译器在O1优化下,把sigsetjmp与siglongjmp之间的局部变量赋值操作丢掉了。
对比:左侧gcc O0,右侧gcc O1
手册中已有说明,满足三个条件的变量赋值无效:
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发现。
Postgresql中的存在大量PG_TRY/PG_CATCH宏的使用:
例如
这类宏是对sigsetjmp、siglongjmp函数的一层封装:(这里给一段PG10的定义,比较简单)
// 全局变量
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也在注释中做了提示。