• longjmp导致局部变量丢失


    0 总结

    • longjmp与setjmp语句之间的变量赋值,在O0下正常,在O1、2下丢失。
    • 变量须满足:
      1. 在调用setjmp函数中的局部变量(栈变量) ,全局变量不受影响
      2. 非volatile

    局部变量有几种情况需要特殊说明:

    • 局部变量 且 操作的是变量的值:会丢失。
    • 局部变量 且 操作的是变量引用的地址:不会丢失。

    请参考文章末尾的例子。


    解决方法:加volatile, 或 对局部变量的操作放在setjmp与longjmp外。


    1 问题复现

    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    使用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
    
    • 1
    • 2
    • 3

    使用O0编译

    执行结果:符合预期

    $ gcc -o main3 -Wall -g -ggdb -O0 -g3 -gdwarf-2 main3.c
    $ ./main3
    a = 10,b = 20,c = 30,d = 40, e = 50
    
    • 1
    • 2
    • 3

    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 
    
           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.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    解法很简单:加volatile

    这类一旦发生很难排查,事后排查难度远大于代码review发现。

    3 对PG的影响

    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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    对于这几个宏,在使用时需要注意:

    如果在PG_TRY里面修改了栈变量,一定要确认变量加了volatile,全局变量不受影响。
    在这里插入图片描述

    新版本的PG也在注释中做了提示。
    在这里插入图片描述

    4 补充用例:函数参数赋值是否失效?

    结论:

    • 传值的参数也会失效,等价与局部变量。
    • 操作指针地址数据的不会失效。
    • 操作指针值的也会失效。
    #include 
    #include 
    #include 
    
    struct TEST
    {
    	int d;
    	int e;
    };
    
    void func(int a, int *b)
    {
    	sigjmp_buf local_sigjmp_buf;
    
    	struct TEST *p;
    	p = (struct TEST*) malloc(sizeof(struct TEST));
    	p->d = 4;
    	p->e = 5;
    
    
    	struct TEST *q;
    	q = (struct TEST*) malloc(sizeof(struct TEST));
    	q->d = 400000;
    	q->e = 500000;
    
    	int c = 3;
    	if (sigsetjmp(local_sigjmp_buf, 0) == 0)
    	{
    		
    		a = 10;
    		*b = 20;
    		c = 30;
    		p->d = 40;
    		q = p;
    		siglongjmp(local_sigjmp_buf, 1);
    	}
    	else
    	{
    		printf("a = %d,b = %d, c = %d, p->d = %d, q->e = %d\n", a, *b, c, p->d, q->e);
    	}
    }
    
    int main()
    {
    	int x = 2;
    	func(1, &x);
    	return 0;
    }
    
    // gcc -o main4 -Wall -g -ggdb -O0 -g3 -gdwarf-2 main4.c
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    结果

    $ gcc -o main4 -Wall -g -ggdb -O0 -g3 -gdwarf-2 main4.c
    $ ./main4
    a = 10,b = 20, c = 30, p->d = 40, q->e = 5
    
    $ gcc -o main4 -Wall -g -ggdb -O1 -g3 -gdwarf-2 main4.c
    $ ./main4
    a = 1,b = 20, c = 3, p->d = 40, q->e = 500000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    【LLM工程篇】deepspeed | Megatron-LM | fasttransformer
    那么多SSM框架整合,这篇用心整理的SSM框架笔记不会还有程序员没看过吧?
    Python使用pymysql来操作MySQL
    短视频背后的商业机遇:TikTok值得投资吗?
    阿里云Maven和Gradle仓库最新配置
    MySQL间隙锁深入分析
    Mybatis - 一对多/多对一映射
    如何将分布式锁性能提升100倍【含面试题】
    CH9101芯片应用—硬件设计指南
    STM32-初识嵌入式
  • 原文地址:https://blog.csdn.net/jackgo73/article/details/127927084