搜索
bottom↓
回复: 25

一个C语言子程序 变量 被“优化”的疑问 【加上volatile解决】

[复制链接]

出0入213汤圆

发表于 2022-4-29 21:21:21 | 显示全部楼层 |阅读模式
本帖最后由 jyrpxj 于 2022-4-29 21:53 编辑

例:定义了一个全局变量,但是这个变量 只在某子程序以及中断里面用到。

unsigned long adcyc; //定义了一个变量

unsigned int adsum;

unsigned char st_start;

void a20msloop(void) //子程序
{
adcyc=0;
  st_start=1;
  NOP();NOP();
  while(st_start==1)  //在中断里面,会跟据这个标志位来改变adcyc的值,并在一定条件下清除该标志
  {
    CLRWDT();NOP(); //等待在这里是为了让中断处理好adcyc
  }
adsum=adcyc/1200; // 如果adcyc 这个变量除了中断跟这个子程序里使用,那么 依据上面那句adcyc=0;  这句的效果等效于adsum=0;
}

void isr(void) __interrupt(0) //中断入口  定时循环进入
{
  if(st_start==1)  //在这里如果st_start==1,则改变adcyc,并一定条件下使st_start=0
  {
    adcyc=......  //这里改变了adcyc
   if( ... )st_start=0;  //这里在满足条件下结束改变adcyc
  }

}

void main (void)  //主程序
{
   //....
   while(1)
  {
    a20msloop( );
    //得到需要的变量adsum  由于上面被“优化”,这里adsum始终为0.
  }
}

请问坛里的大佬,这样被意外优化掉了,合理吗? 还是说写这样的代码就是不合规范的?





*********************为了不被优化做的改动*******************************
做以下改动,就不会被优化了。
void a20msloop(void) //子程序
{
//   注释掉 adcyc=0; 把这句移动 调用这个子程序前
  st_start=1;
  NOP();NOP();
  while(st_start==1)  //在中断里面,会跟据这个标志位来改变adcyc的值,并在一定条件下清除该标志
  {
    CLRWDT();NOP(); //等待在这里是为了让中断处理好adcyc
  }
adsum=adcyc/1200; // 这样这句话就不会被优化为adsum=0
}

void main (void)  //主程序
{
   //....
   while(1)
  {
    adcyc=0;  //移到这里来

    a20msloop( );
    //得到需要的变量adsum  这样才得到正确的adsum值
  }
}

===========解决了=================
【全局变量前加上volatile,解决】

阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。

出0入213汤圆

 楼主| 发表于 2022-4-29 21:31:55 | 显示全部楼层
妈的,只要我在子程序里这么用 ,就给我优化了 ,怎么会有这样的编译器,编译器选项里面关了所有优化。

void a20msloop(void) //子程序
{
adcyc=0;
...........
...........
//等待过程 中断会改变adcyc
.......
adsum=adcyc/1200; //只要有上面那句adcyc=0;  这句就给我优化成 adsum=0;  全然不顾中断是会改变adcyc
}

出0入442汤圆

发表于 2022-4-29 21:40:25 | 显示全部楼层
给全局变量加上volatile。。老生常谈的问题了。

出0入213汤圆

 楼主| 发表于 2022-4-29 21:52:22 | 显示全部楼层
wye11083 发表于 2022-4-29 21:40
给全局变量加上volatile。。老生常谈的问题了。
(引用自3楼)

感谢感谢,第一次掉进这样的坑。 试了一下 给全局变量加上volatile 果然有用。错的竟是我,是我错怪编译器了。

出300入477汤圆

发表于 2022-4-30 09:32:08 来自手机 | 显示全部楼层
本帖最后由 redroof 于 2022-4-30 09:43 编辑
jyrpxj 发表于 2022-4-29 21:52
感谢感谢,第一次掉进这样的坑。 试了一下 给全局变量加上volatile 果然有用。错的竟是我,是我错怪编译 ...
(引用自4楼)


不然编译器的常量传播都不能用了。这是个很重要的优化。

如果一个变量会在当前上下文中无法看见的地方被改变,比如中断里,那么它就得加volatile。不然编译器看它在本函数里一直没变,就直接在编译时传播它的值了。
如果编译器不做这个规定,允许任何不做标记的变量在任何时候都可能被中断随便改变,那么编译器就真的啥优化都没法做了。
你自己想想,每个变量每次使用都得从内存读取,编译器还能优化什么。
为了让编译器能把变量缓存在寄存器里,或者直接把常量传播到每个赋值目标,你必须告诉编译器哪些是特殊的不可优化的变量。除了这些特殊的变量以外,别的变量都是不会被意外改变的,只能在编译器看得到的地方改变。
这个特殊标记就是volatile

出1310入193汤圆

发表于 2022-4-30 09:47:24 | 显示全部楼层
中断里改变的变量,必须加volatile  硬性规定

出0入0汤圆

发表于 2022-4-30 16:06:37 | 显示全部楼层
主循环操作最好关中断再操作,然后再开中断。

出300入477汤圆

发表于 2022-4-30 16:21:30 来自手机 | 显示全部楼层
本帖最后由 redroof 于 2022-4-30 16:25 编辑
prince2010 发表于 2022-4-30 16:06
主循环操作最好关中断再操作,然后再开中断。
(引用自7楼)


32位机操作32位变量不用关中断,天生就是原子的。当然如果又有标志位又有计数器,主程序里要注意操作的顺序,先检查哪个,先改哪个。要注意主程序的任何位置都可能被中断打断。
实在不放心就关中断吧。
一个公理:如果你不能确定对共享变量的操作是否可以不关中断,那么就关。多线程的情况下如果不能确定是不是可以不加锁,那么就加。
多执行几行程序比偶尔岀错要好多了,哈哈

出20入25汤圆

发表于 2022-4-30 16:35:40 来自手机 | 显示全部楼层
redroof 发表于 2022-4-30 16:21
32位机操作32位变量不用关中断,天生就是原子的。当然如果又有标志位又有计数器,主程序里要注意操作的顺 ...
(引用自8楼)

比如ARM的,好像byt e也是原子操作

出0入0汤圆

发表于 2022-4-30 17:26:08 | 显示全部楼层
redroof 发表于 2022-4-30 16:21
32位机操作32位变量不用关中断,天生就是原子的。当然如果又有标志位又有计数器,主程序里要注意操作的顺 ...
(引用自8楼)

像ARM这种load-store架构的,修改一个32位变量,如:

i = 100;

这样的C言代码是原子操作?

出300入477汤圆

发表于 2022-4-30 17:28:14 来自手机 | 显示全部楼层
chenchaoting 发表于 2022-4-30 16:35
比如ARM的,好像byt e也是原子操作
(引用自9楼)

短于自身字长的,当然更是原子的啊

出300入477汤圆

发表于 2022-4-30 17:32:53 来自手机 | 显示全部楼层
本帖最后由 redroof 于 2022-4-30 17:35 编辑
prince2010 发表于 2022-4-30 17:26
像ARM这种load-store架构的,修改一个32位变量,如:

i = 100;

(引用自10楼)


是的,赋值是原子的,意思是你从别处或者见到新值,或者见到旧值,不会读到半新半旧的值。
但给一个变量加一就不是原子的。从两个地方同时加一,可能被中断导致只加了一次。
但是从中断里增加它,从主程序里清零是安全的,因为中断不会被主程序打断。

出0入0汤圆

发表于 2022-4-30 17:52:26 | 显示全部楼层
redroof 发表于 2022-4-30 17:32
是的,赋值是原子的,意思是你从别处或者见到新值,或者见到旧值,不会读到半新半旧的值。
但给一个变量 ...
(引用自12楼)

你说的“赋值是原子的”,只是指“不会读到半新半旧的值”吧?而不是指汇编指令吧?

因为我仿真这条赋值语句对应的汇编指令有3条,汇编指令之间总是能被中断吧?

出0入0汤圆

发表于 2022-4-30 18:36:47 | 显示全部楼层
redroof 发表于 2022-4-30 16:21
32位机操作32位变量不用关中断,天生就是原子的。当然如果又有标志位又有计数器,主程序里要注意操作的顺 ...
(引用自8楼)

32位机操作32位变量还真不一定是原子操作,要不ARM也不会搞互斥访问指令了

出0入0汤圆

发表于 2022-4-30 18:40:53 | 显示全部楼层
prince2010 发表于 2022-4-30 17:52
你说的“赋值是原子的”,只是指“不会读到半新半旧的值”吧?而不是指汇编指令吧?

因为我仿真这条赋值 ...
(引用自13楼)

只要不是读内存、修改、写内存就没事,寄存器有堆栈保护

出300入477汤圆

发表于 2022-4-30 19:17:50 来自手机 | 显示全部楼层
本帖最后由 redroof 于 2022-4-30 19:20 编辑
modbus 发表于 2022-4-30 18:36
32位机操作32位变量还真不一定是原子操作,要不ARM也不会搞互斥访问指令了 ...
(引用自14楼)


互斥指令就是为了实现原子的自增。或者说原子的读改写。

出300入477汤圆

发表于 2022-4-30 19:27:43 来自手机 | 显示全部楼层
prince2010 发表于 2022-4-30 17:52
你说的“赋值是原子的”,只是指“不会读到半新半旧的值”吧?而不是指汇编指令吧?

因为我仿真这条赋值 ...

(引用自13楼)

能被中断并不是问题啊,说它是原子的仅仅是管结果。这三条指令不管在什么地方被中断,数据本身都不会被破坏。两条这样的指令被交叉执行,结果也仍然没有问题。那么这就是原子的。

出0入0汤圆

发表于 2022-4-30 20:41:03 | 显示全部楼层
redroof 发表于 2022-4-30 19:27
能被中断并不是问题啊,说它是原子的仅仅是管结果。这三条指令不管在什么地方被中断,数据本身都不会被破 ...
(引用自17楼)

好吧,可能是我搞错了,我一直以为原子操作只能通过关中断、信号量等措施,或者cpu本身有test-and-set指令才行。

出300入477汤圆

发表于 2022-4-30 21:27:28 来自手机 | 显示全部楼层
prince2010 发表于 2022-4-30 20:41
好吧,可能是我搞错了,我一直以为原子操作只能通过关中断、信号量等措施,或者cpu本身有test-and-set指 ...
(引用自18楼)

这种简单赋值,确实是原子的,但这只是说你执行这种动作不用额外保护而已。只用这种操作无法在多个线程之间传递数据。所以无法用来实现通常意义上的无锁数据结构。
可以在多线程之间传递数据的原子操作,也就是原子自加自减,原子test and set等等,这些需要cpu特殊实现,否则只能用关中断来做了。
信号量的内部也是有个原子自加自减的计数器的。

出0入0汤圆

发表于 2022-5-3 11:12:56 | 显示全部楼层
redroof 发表于 2022-4-30 09:32
不然编译器的常量传播都不能用了。这是个很重要的优化。

如果一个变量会在当前上下文中无法看见的地方被 ...
(引用自5楼)

这个讲解非常到位,感谢
我标记下,有空再来体会

出300入477汤圆

发表于 2022-5-3 16:19:29 来自手机 | 显示全部楼层
擦鞋匠 发表于 2022-5-3 11:12
这个讲解非常到位,感谢
我标记下,有空再来体会

(引用自20楼)

一个变量会被当前不可见的地方随时改变,对做编译器优化的人来说,是非常坑人的事情。如果要考虑这个,基本上就啥优化都不能做了。对变量的每个读都得读内存,每个中间变量写入也得写内存。基本上就变成最简单的小儿科级别的编译器了,按输入的C代码一行一行直接变成汇编就行。别的想做也没法做了。

出0入0汤圆

发表于 2022-5-5 10:28:12 | 显示全部楼层
redroof 发表于 2022-4-30 09:32
不然编译器的常量传播都不能用了。这是个很重要的优化。

如果一个变量会在当前上下文中无法看见的地方被 ...
(引用自5楼)

您好,请教一下,关于这个问题,我延申了一下,采用多线程来测试。
环境:ubuntu 18.04, GCC优化等级开到最高,-O3,没有用volatile来申明,但是结果和楼主的现象不一样。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <pthread.h>

  5. unsigned long adcyc;
  6. unsigned int adsum;
  7. unsigned char st_start;

  8. static void a20msloop(void)
  9. {
  10.     adcyc = 0;
  11.     st_start = 1;

  12.     while (st_start == 1)
  13.     {
  14.         usleep(1);
  15.     }
  16.     adsum = +adcyc;
  17.     printf("adsum:%d\r\n", adsum);
  18. }

  19. static void* thread_fun1(void *arg)
  20. {
  21.     while (1)
  22.     {
  23.         a20msloop();
  24.     }

  25.     return NULL;
  26. }

  27. static void isr(void)
  28. {
  29.     if (st_start == 1)
  30.     {
  31.         adcyc = 3;
  32.         st_start = 0;
  33.     }
  34. }

  35. void* thread_fun2(void *arg)
  36. {
  37.     while (1)
  38.     {
  39.         sleep(1);
  40.         isr();
  41.     }

  42.     return NULL;
  43. }

  44. int main(int argc, char **argv)
  45. {
  46.     pthread_t thread1, thread2;

  47.     pthread_create(&thread1, NULL, thread_fun1, NULL);
  48.     pthread_create(&thread2, NULL, thread_fun2, NULL);

  49.     void *ret = NULL;
  50.     pthread_join(thread1, &ret);
  51.     pthread_join(thread2, &ret);

  52.     return 0;
  53. }
复制代码


运行结果:
adsum:3
adsum:3
adsum:3
adsum:3
adsum:3

出300入477汤圆

发表于 2022-5-5 10:39:41 来自手机 | 显示全部楼层
syj0925 发表于 2022-5-5 10:28
您好,请教一下,关于这个问题,我延申了一下,采用多线程来测试。
环境:ubuntu 18.04, GCC优化等级开 ...

(引用自22楼)

我在电脑上基本也不写volatile,因为电脑的编译器默认对全局变量似乎都很保守。只要你中间调用了函数,它看不见这个函数的内容,就假定这个函数会改变所有的全局变量。这样效果就对了。
你当作主程序的部分总是要调用系统sleep函数的。
要不你试一下,不调用任何系统函数,用死循环来延时,这样应该就能看到和单片机一样的效果了。

出0入0汤圆

发表于 2022-5-5 13:33:18 | 显示全部楼层
redroof 发表于 2022-5-5 10:39
我在电脑上基本也不写volatile,因为电脑的编译器默认对全局变量似乎都很保守。只要你中间调用了函数,它 ...
(引用自23楼)

确实如您所说的一样!去掉了while中的usleep函数,结果线程1就在while中一直死循环了(和楼主的现象一样)

然后我进一步验证,把usleep函数换成调用自己随便写的一个函数,结果也是线程1在while中死循环。


很奇怪:编译器对调用usleep就不优化,但是对没有调用,或者调用其它自己写的测试函数,就进行优化。

出300入477汤圆

发表于 2022-5-5 14:11:03 | 显示全部楼层
syj0925 发表于 2022-5-5 13:33
确实如您所说的一样!去掉了while中的usleep函数,结果线程1就在while中一直死循环了(和楼主的现象一样)
...
(引用自24楼)

因为系统库函数是看不到内部代码的啊,所以编译器不可能知道这个函数里面有没有操作你外面这个全局变量,为了保险起见必须假设他会操作。所以必须在调用它之前把全局变量都写回,然后调用完了再重新读取。
你自己同一个文件里面定义的函数,编译器看得到函数内容,知道它有没有操作这个全局变量,那么就可以优化了。
不信你把这个函数的定义放到另一个文件里面去,如果编译器没开全程序优化,就无法看见这个函数的内容,然后就跟调用库函数一样必须用保守的假设了。

出0入0汤圆

发表于 2022-5-5 15:03:53 | 显示全部楼层
redroof 发表于 2022-5-5 14:11
因为系统库函数是看不到内部代码的啊,所以编译器不可能知道这个函数里面有没有操作你外面这个全局变量, ...
(引用自25楼)

感谢您详细解答,学到很多了! 平时编译优化等级都不敢开,就怕遇到未知风险。。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-29 17:59

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表