为什么 GCC 编译器省略了一些代码?

电器工程 avr C avr-gcc 优化 海合会
2022-01-17 18:11:27

我不明白为什么 GCC 编译器会删除我的部分代码,而它在附近保留了完全相同的代码?

C代码:

#define setb_SYNCO do{(PORTA|= (1<<0));} while(0);

ISR(INT0_vect){
    unsigned char i;

    i = 10;
    while(i>0)i--;   // first pause - omitted

    setb_SYNCO;
    setb_GATE;
    i=30;
    clrb_SYNCO;
    while(i>0)i--;  // second pause - preserved
    clrb_GATE;
}

LSS(汇编文件,由编译器创建)的对应部分:

ISR(INT0_vect){
  a4:   1f 92           push    r1
  a6:   0f 92           push    r0
  a8:   0f b6           in  r0, 0x3f    ; 63
  aa:   0f 92           push    r0
  ac:   11 24           eor r1, r1
  ae:   8f 93           push    r24
    unsigned char i;

    i = 10;
    while(i>0)i--;

    setb_SYNCO;
  b0:   d8 9a           sbi 0x1b, 0 ; 27
    setb_GATE;
  b2:   d9 9a           sbi 0x1b, 1 ; 27
    i=30;
    clrb_SYNCO;
  b4:   d8 98           cbi 0x1b, 0 ; 27
  b6:   8e e1           ldi r24, 0x1E   ; 30
  b8:   81 50           subi    r24, 0x01   ; 1
    while(i>0)i--;
  ba:   f1 f7           brne    .-4         ; 0xb8 <__vector_1+0x14>
    clrb_GATE;
  bc:   d9 98           cbi 0x1b, 1 ; 27
}
  be:   8f 91           pop r24
  c0:   0f 90           pop r0
  c2:   0f be           out 0x3f, r0    ; 63
  c4:   0f 90           pop r0
  c6:   1f 90           pop r1
  c8:   18 95           reti

我可以假设编译器发现这样的代码是虚拟的并将其删除,但为什么它在代码末尾保留相同的代码?

是否有任何编译器指令可以防止这种优化?

4个回答

您可以尝试使循环实际执行某些操作。就目前而言,编译器非常正确地说“这个循环什么都不做 - 我会摆脱它”。

因此,您可以尝试我经常使用的构造:

int i;
for (i = 0; i < 10; i++) {
    asm volatile ("nop");
}

注意:并非所有 gcc 编译器的目标都使用相同的内联汇编语法——您可能需要针对您的目标对其进行调整。

由于在一条评论中您声明“每个 CPU 滴答声都是值得的”,因此我建议使用一些内联汇编来使您的延迟循环按照您的意愿进行。该解决方案优于各种解决方案,volatile或者-O0因为它清楚地表明了您的意图。

unsigned char i = 10;
__asm__ volatile ( "loop: subi    %0, 0x01\n\t"
                   "      brne    loop"
                   : "+rm" (i)
                   : /* no inputs */
                   : /* no dirty registers to decleare*/);

这应该够了吧。不稳定的东西是告诉编译器“我知道这不会做任何事情,只要保留它并相信我”。这三个 asm “语句”非常不言自明,您可以使用任何寄存器而不是 r24,我相信编译器喜欢较低的寄存器,因此您可能想要使用较高的寄存器。在第一个之后,:您应该列出输出(读取和写入)c 变量,但没有,在第二个之后,:您应该列出输入(仅)c 变量,再次,没有,第三个参数是逗号分隔的修改寄存器列表,在本例中为 r24。我不确定你是否也应该包括状态寄存器,因为ZERO标志当然会改变,我没有包括它。

根据 OP 的要求编辑已编辑的答案。一些笔记。

"+rm"before(i)意味着您让编译器决定将 i 放在内存寄存器在大多数情况下这是一件好事,因为如果它是免费的,编译器可以更好地优化。在您的情况下,我相信您只想保留 r 约束以强制 i 成为寄存器。

是的,你可以假设。如果将变量 i 声明为 volatile,则告诉编译器不要对 i 进行优化。

第一个循环之后i是一个常数。i和循环的初始化只产生一个常数值。标准中没有规定必须按原样编译此循环。该标准也没有说明时间。编译后的代码必须表现得好像循环存在并且确实存在。您无法可靠地判断此优化是在标准下执行的(时间不计算在内)。

第二个循环也应该被删除。我认为它不是一个错误(或缺少优化)。在循环之后i是恒定的零。代码应替换为设置i为零。

我认为 GCC 保留i纯粹是因为您执行(不透明)端口访问可能会影响i.

采用

asm volatile ("nop");

诱骗 GCC 相信循环做了一些事情。