欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

错误之编译器优化问题

程序员文章站 2023-12-25 11:02:09
...

一、碰到的问题

前段时间在编写代码的时候,碰到这个问题,我在中断中对变量赋值 RE_Flag=1,然后在其他的一个任务中执行这行代码while(RE_Flag==0){},按照我的理解:这个任务中会等待RE_Flag=1时,然后才会执行后面的代码,但是我调试发现,程序一直卡在 while(RE_Flag ==0){}这行代码,但是RE_Flag这个变量已经变成了1,但是为什么一直卡在while这呢?

二、变量关键字

用volatile关键字是防止变量被编译器优化
volatile 是在C ,C++,Java等中语言中的一种修饰关键字。
这个关键字在嵌入式系统中,是一个非常重要的一个使用。但是在单片机中,如果不熟悉这个关键字,很有可能产生想像不到的意外。就像我上面的现象…
关于volatile的意义,根据标准C的定义:
volatile的目的是,避免进行默认的优化处理.比如说对于编译器优化的功能,如果从编译器看来,有些多余的代码的话,编译器就会启动优化程序,并删除一些代码,但是这在嵌入式系统中很有可能是关键性的处理,必须不能保证被编译器删掉,所以提供了Volitile来声明,告诉编译器无论如何都不要删掉我。
像我上面的代码:

uint8_t flag;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)//中断
{
	if(GPIO_Pin == A7106_IRQ_Pin)
	{
		flag=1;
	}
}

void function(void//任务函数
{
	....
	while(RE_Flag ==0){}
	.....
}

此时没有在任务中改变这里的RE_Flag 的值,这样的话,RE_Flag 看起来就像是多余的,因此单片机编译器可能把此程序看为下段程序

uint8_t flag;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)//中断
{
	if(GPIO_Pin == A7106_IRQ_Pin)
	{
		flag=1;
	}
}

void function(void//任务函数
{
	....
	if(RE_Flag==0)
	{
		while(1){...}
	} 
	.....
}

对于一般的编译器,一般都会把程序优化成上述程序。 优化while循环使其不必每次判断条件
这样的优化确实可以提高代码速度,但是我的本意是想等待他为1就跳出循环,这样优化的话,那么自然不会跳出循环。
为了避免这种情况,我们使用volatile关键字来防止程序被编译器优化,只要在变量前增加关键字即可:

volatile uint8_t flag;

这位博主讲解的很不错:https://blog.csdn.net/fengyunjh6/article/details/9055359

三、volatile关键字的使用

这里推荐一篇知乎上详解:https://zhuanlan.zhihu.com/p/24402180
详细的介绍了 ARM C语言编程优化策略(KEIL平台)

没有使用 volatile 修饰变量时:

int buffer_full;
int read_stream(void)
{
    int count = 0;
    while (!buffer_full)
    {
        count++;
    }
    return count;
}

-O2 优化等级其汇编代码为:

read_stream PROC
LDR r1, |L1.28|
MOV r0, #0
LDR r1, [r1, #0]
|L1.12|
CMP r1, #0
ADDEQ r0, r0, #1
BEQ |L1.12| ; infinite loop
BX lr
ENDP
|L1.28|
DCD ||.data||
AREA ||.data||, DATA, ALIGN=2
buffer_full
DCD 0x00000000

使用 volatile 修饰 buffer_full 时,-O2下汇编代码为:

read_stream PROC
LDR r1, |L1.28|
MOV r0, #0
|L1.8|
LDR r2, [r1, #0]; ; buffer_full
CMP r2, #0
ADDEQ r0, r0, #1
BEQ |L1.8|
BX lr
ENDP
|L1.28|
DCD ||.data||
AREA ||.data||, DATA, ALIGN=2
buffer_full
DCD 0x00000000

可以看到,没有使用 volatile 时,while 循环查询的变量被优化了(只查询一次变量,并把它存在寄存器中,循环结束条件判断直接和保存变量值的寄存器进行比较,而不再更新寄存器的值),而 volatile 修饰后,则每次循环都查询(可以在汇编中看到,每一次循环体执行开始,首先会加载变量值到寄存器中)。
特别是在中断、多线程及寄存器读取中一定要注意使用 volatile 修饰易变的变量。

四、keil中优化等级

错误之编译器优化问题
这是keil中优先等级的设置
-O0:最少的优化,可以最大程度上配合产生代码调试信息,可以在任何代码行打断点,特别是死代码处。

-O1:有限的优化,去除无用的inline和无用的static函数、死代码消除等,在影响到调试信息的地方均不进行优化。在适当的代码体积和充分的调试之间平衡,代码编写阶段最常用的优化等级。

-O2:高度优化,调试信息不友好,有可能会修改代码和函数调用执行流程,自动对函数进行内联等。

-O3:最大程度优化,产生极少量的调试信息。会进行更多代码优化,例如循环展开,更激进的函数内联等。

本文主要为记录平常编写代码的错误问题,如有侵权和错误之处,请联系作者,谢谢!

上一篇:

下一篇: