【问题标题】:Can an ANSI C compiler remove a delay loop?ANSI C 编译器可以删除延迟循环吗?
【发布时间】:2013-01-21 23:31:02
【问题描述】:

考虑 ANSI C 中的 while 循环,其唯一目的是延迟执行:

unsigned long counter = DELAY_COUNT;
while(counter--);

我已经看到这经常用于在嵌入式系统上强制延迟,例如。没有sleep 函数和定时器或中断是有限的。

我对 ANSI C 标准的解读是,它可以被符合标准的编译器完全删除。它没有5.1.2.3中描述的副作用:

访问 volatile 对象、修改对象、修改文件或调用执行任何这些操作的函数都是副作用,即执行环境状态的变化。

...这部分还说:

如果一个实际的实现可以推断出它的值没有被使用并且没有产生所需的副作用(包括调用函数或访问易失性对象引起的任何副作用),则它不需要评估表达式的一部分。

这是否意味着可以优化循环?即使countervolatile

注意事项:

  1. 这与Are compilers allowed to eliminate infinite loops? 不太一样,因为它指的是无限 循环,并且会出现关于何时允许程序终止的问题。在这种情况下,程序肯定会在某个时候越过这条线,无论是否优化。
  2. 我知道 GCC 做了什么(删除 -O1 或更高版本的循环,除非 countervolatile),但我想知道标准规定的内容。

【问题讨论】:

  • 只要 可观察 行为没有改变,C 标准就不会禁止优化这个循环。所以标准没有具体规定。

标签: c optimization standards c89


【解决方案1】:

C 标准遵从遵循“as-if”规则,通过该规则,编译器可以生成任何行为“好像”它在抽象机器上运行您的实际指令的代码。由于不执行任何操作具有与执行循环“好像”相同的可观察行为,因此完全允许不为其生成代码。

换句话说,在真机上进行计算所花费的时间并不是程序“可观察”行为的一部分,它只是特定实现的一种现象。

volatile 变量的情况有所不同,因为访问 volatile 算作“可观察”效果。

【讨论】:

  • @KingsIndian:呵呵,别担心,我认为答案的多样性总是一件好事:-)
【解决方案2】:

这是否意味着可以优化循环?

是的。

即使计数器是不稳定的?

没有。它会读取和写入一个具有可观察行为的 volatile 变量,因此它必须发生。

【讨论】:

    【解决方案3】:

    如果计数器为volatile,则编译器无法合法优化延迟循环。否则可以。

    这样的延迟循环很糟糕,因为它们燃烧的时间取决于编译器如何为它们生成代码。使用不同的优化选项,您可以实现不同的延迟,这几乎不是延迟循环想要的。

    出于这个原因,这种延迟循环应该用汇编语言实现,程序员可以完全控制代码。这通常适用于具有简单 CPU 的嵌入式系统。

    【讨论】:

    • 你是否用汇编语言实现这个在某种意义上是无关紧要的。依赖于一定数量的迭代的延迟循环是不稳定的——“延迟”根据许多因素而变化,几乎所有这些因素都超出了程序员的控制范围。 不要使用这样的延迟循环。
    • @NikBougalis 不一定正确。如果 CPU 是一个相对愚蠢的 CPU,并且每次使用相同数量的时钟执行指令(因为它是愚蠢的并且没有缓存)并且如果时钟速率没有改变,那么延迟将非常稳定,除非中断被启用并且 ISR 频繁执行并花费大量时间。并且在硬件初始化期间,通常会暂时禁用中断或直到初始化完成之后。如果满足所有这些条件,则延迟循环没有问题。
    • @NikBougalis 真的。但由于 OP 明确提到了嵌入式,所以不排除。
    • FTR,我当然同意这是一种糟糕的方法,但我正在尝试确定它们是否存在更危险的问题。顺便说一句,即使在高度受控的嵌入式系统中,我也看到了这种技术的混合结果,无论是优化非常有限的“愚蠢”编译器,还是更智能的编译器。有时问题就像@NikBougalis 所描述的那样 - 循环在那里,但延迟与DELAY_COUNT 完全不是线性的,并且可以随着周围的代码而改变。有时情况恰恰相反:延迟是线性的,但最小的单位比您预期的要长得多。
    • (我什至不会在适当的多任务操作系统上尝试它——即使sleep 在你应该使用例如select 或互斥锁时也有点笨拙。)
    【解决方案4】:

    标准规定了您所看到的行为。如果你为DELAY_COUNT 创建一个依赖树,你会看到它有一个没有使用的修改属性,这意味着它可以被消除。这是参考非易失性案例。在 volatile 情况下,编译器无法使用依赖树来尝试删除此变量,因此延迟仍然存在(因为 volatile 意味着硬件可以更改内存映射值,或者在某些情况下意味着“我真的需要这个不要扔它away") 如果你正在查看是否标记为 volatile,它会告诉编译器,请不要把它扔掉,它的存在是有原因的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-01-01
      • 2017-11-05
      • 1970-01-01
      • 2015-10-06
      • 1970-01-01
      • 1970-01-01
      • 2017-02-05
      相关资源
      最近更新 更多