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


9

我不明白为什么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

我可以假设编译器发现此类代码是伪代码,并将其剪切掉,但为什么在代码末尾保留相同的代码?

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


1
您还可以告诉编译器不要优化单个函数,也许值得在ISR中尝试此方法。请参阅关于stackoverflow的问题。
弗拉基米尔·克拉韦罗

2
嗨,罗曼,我在您的问题中添加了“ c”标签,以删除atmega。因为有限制(五个),所以我不得不删除一个标签,并且在询问与代码相关的问题时,将语言名称添加为标签非常好,因为所有代码(Q&A)都会突出显示。
弗拉基米尔·克拉韦罗

5
一般而言,高级语言(如C)被明确设计为与其生成的程序集以1:1关系绑定。如果您需要计算指令数以确保正确的时间安排,则始终必须依靠汇编程序(如某些答案所做的那样)。高级语言的全部要点是,编译器具有一定的自由度,可以帮助您使代码比以前更快,更强大。诸如寄存器分配和分支预测之类的细节最好留给编译器……除了在这种情况下,您(程序员)确切地知道您想要的指令。
Cort Ammon 2015年

5
更好的问题是,GCC为什么不优化两个循环?
Ilmari Karonen

5
Gcc首先展开循环,然后才注意到相应的代码是无用的。循环大小为30时,展开将是愚蠢的,而gcc不会这样做。在更高的优化级别上,两者都被优化掉了。
Marc Glisse 2015年

Answers:


9

因为在一个评论中您声明“每个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"之前(i)意味着你是让编译器决定的地方,我在埃默里或在[R egister。在大多数情况下,这是一件好事,因为如果免费,编译器可以更好地进行优化。在您的情况下,我相信您只想保留r约束以强制i成为寄存器。


看起来这是我真正需要的东西。但是,您可以修改答案以接受任何c变量,而不是10原始答案中提到的文字吗?我正在尝试阅读有关正确使用asm构造的GCC手册,但现在对我来说有些模糊。我将不胜感激!
罗曼·马特维耶夫

1
@RomanMatveev根据您的要求进行编辑
Vladimir Cravero 2015年

13

您可以尝试使循环实际执行某些操作。从目前的角度来看,编译器正确地说:“此循环无所作为-我将摆脱它”。

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

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

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


您的解决方案似乎比我的解决方案优雅得多……如果需要精确的周期计数,也许我的解决方案更好?我的意思是,不能完全以某种方式进行编译,是吗?
弗拉基米尔·克拉韦罗

8
使用C的简单事实意味着您无法保证循环。如果您需要精确的周期计数,那么ASM是唯一的方法。我的意思是,你用C环不同的时间,如果你启用了-funroll,环比如果你不这样做,等等
Majenko

是的,这就是我的想法。当我使用足够高的i值(100或更大)进行硬件延迟时,我想您的解决方案在提高可读性的同时实际上会产生相同的结果。
弗拉基米尔·克拉韦罗

6

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


1
我认为这并非完全正确。
弗拉基米尔·克拉韦罗

1
@VladimirCravero,你这么说是什么意思?你能说清楚一点吗?
罗曼·马特维耶夫

2
我的意思是,我不确定编译器的功能。声明一个变量volatile告诉编译器它可能会在其他地方更改,因此它确实应该在此之前进行更改。
弗拉基米尔·克拉韦罗

1
@罗马Matveev register unsigned char volatile i __asm__("r1");也许?
a3f

2
声明i为volatile可以解决所有问题。这由C标准5.1.2.3保证。如果i是易变的,则合格的编译器不得优化掉这些循环。幸运的是,GCC是合格的编译器。不幸的是,有许多可能的C编译器不符合该标准,但这与该特定问题无关。绝对不需要内联汇编程序。
伦丁2015年

1

在第一个循环之后i是一个常量。i和循环的初始化除了产生恒定值外什么也不做。标准中没有任何内容指定必须按原样编译此循环。该标准也没有说明任何有关计时的信息。编译后的代码必须表现出好像存在循环一样并且确实存在。您无法可靠地说出此优化是在标准下执行的(计时不计算在内)。

第二个循环也应删除。我认为这不是一个错误(或缺少优化)。循环后i常数为零。该代码应替换为设置i为零。

我认为GCC i纯粹是出于执行(不透明)端口访问可能会影响的原因i

采用

asm volatile ("nop");

诱使GCC相信该循环会有所作为。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.