【问题标题】:Do math functions of constant expressions get pre-calculated at compile time?常量表达式的数学函数是否在编译时预先计算?
【发布时间】:2011-01-04 04:46:49
【问题描述】:

为了方便和连贯,我倾向于使用常量表达式的数学函数(即log(x)/log(2) 而不是log(x)/0.3...)。由于这些函数实际上并不是语言本身的一部分,因此它们也没有在 math.h 中定义(仅声明),常量函数会在编译时预先计算,还是会在运行时被浪费地计算?

【问题讨论】:

  • 我希望它们会被预先计算,但如果您说出您使用的是什么编译器以便人们可以实际检查,这将有所帮助。
  • 当然,log(x) 在编译时不是常量表达式,除非 x 之前已被声明为常量并被赋予了值。编写没有编译器会评估的代码可能并不难,即使人类很容易这样做。
  • 我已经很久没有遇到这个问题了,但是已经有一些实现允许在运行时指定浮点计算的一些细节。在这种情况下,任何依赖于它的东西都不能在编译时计算出来。

标签: c math compile-time


【解决方案1】:

这取决于编译器和优化标志。正如@AndrewyT 指出的那样,GCC 能够通过属性指定哪些函数是常量和纯函数,在这种情况下,答案是肯定的,它将内联结果,您可以轻松检查:

$ cat constant_call_opt.c 
#include <math.h>

float foo() {
        return log(2);
}

$ gcc -c constant_call_opt.c -S

$ cat constant_call_opt.s 
        .file   "constant_call_opt.c"
        .text
.globl foo
        .type   foo, @function
foo:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $4, %esp
        movl    $0x3f317218, %eax
        movl    %eax, -4(%ebp)
        flds    -4(%ebp)
        leave
        ret
        .size   foo, .-foo
        .ident  "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
        .section        .note.GNU-stack,"",@progbits

那里没有函数调用,只加载一个常量 (0x3f317218 == 0.69314718246459961 == log(2))

虽然我现在手头没有任何其他编译器可以尝试,但我认为您可以期望在所有主要的 C 编译器中都有相同的行为,因为这是一个微不足道的优化。

【讨论】:

  • 如果您无法立即解码 32 位 IEEE 浮点编码,例如 0x3f317218,请尝试编译 log(1.0),这会在 movl $0x0 , %eax 行中生成一个很好的简单常量 $0x0
【解决方案2】:

它们通常会在运行时进行计算(有关如何内联它们,请参阅其他答案),但我不一定会使用“浪费”这个词,除非它们很多和/或代码执行了很多次。

不要只输入常量值,而是创建一个 #defineconst 变量来表达值的含义(PILOG_2 等)并使用它来代替。

例如:

#define LOG_2 0.30102999566

这不做任何计算,您可以根据您的环境(32 位和 64 位)将值指定为您想要或可以管理的任何精度。

【讨论】:

  • 小心!如果您使用#define LOG_2 (log(2)),则在您使用LOG_2 的任何时候都会调用log()const 全局变量是一个非常更好的主意。
  • @Mike - 我不是建议你这样做,而是建议你这样做 #define LOG_2 0.30102999(或者你想要多少 dp)
  • 您断然断言这是错误的,至少对于 GCC,它们是在编译时计算的。
  • 您可以将值指定为任意小数位数,但存储的实际值将被截断为适合所需浮点值(32 位或 64 位)的值。因此,您最好使用尽可能多的小数位,以适应您使用的最精确的数字格式。双精度/64 位浮点值大约是小数点后 11 位。
【解决方案3】:

在库函数的情况下,一些编译器可能将这些函数实现为内在函数,这意味着编译器对函数有足够的了解,可以在编译时用常量替换调用。是否会这样做取决于编译器。在实践中,我经常注意到一些编译器不愿意在编译时预先计算浮点表达式,即使它们不涉及任何函数调用。

在一般情况下,通常它们不会也无法在编译时计算,假设编译器根本不了解这些函数,无法在编译时计算它们。也许它们有一些显着的运行时副作用?

一些编译器可能具有非标准的编译器相关扩展,允许用户向编译器提供有关函数的附加信息,以便编译器可以更好地优化函数调用,甚至确定给定的调用是否可以替换为编译时预计算。例如,GCC 编译器支持constpure 等函数attributes(GCC 特定扩展)。如果参数在编译时已知,则理论上可以用编译时预计算替换具有const 属性的函数调用。虽然我不能说 GCC 是否真的可以做到这一点。

在 C++(即使您的问题被标记为 C)语言中,一个计划中的新功能是 constexpr 声明说明符,它用于类似目的并且应该具有您描述的效果。

【讨论】:

    【解决方案4】:

    一些编译器会在编译时评估它们,但不能保证这种行为(这样做也可能导致问题)。你需要检查你的编译器,看看它做了什么。

    请注意,在许多系统上,log(2) 可通过 &lt;math.h&gt; 中的宏 M_LN2 获得。

    【讨论】:

    • 如果编译器知道 log 没有副作用并且总是返回相同的值(例如,是一个“纯”函数),它可以在编译时评估它们。由于用户可以选择替换数学库中的函数,并且可以选择用每次调用 log(2) 时不返回相同值或具有副作用的函数替换 log,编译器无法评估它在编译时。
    • 当结果准确时(例如pow(2.0, 2.0)),我能够在两个不同的编译器(gcc,icc)上以严格模式获得此行为,因此(理论上)调用是side-假设用户没有替换他们自己的例程,则效果免费。虽然我同意您的分析,即编译器可能不应该这样做,但他们似乎完全愿意这样做 =/。如果某些编译器即使在标志受到影响时也会这样做,我也不会感到惊讶,因为编译器编写者通常对浮点状态的看法相当模糊。
    • 这是允许的,因为(试图)替换任何标准函数的程序不再符合 C 程序。
    【解决方案5】:

    这取决于。如果编译器可以像在运行时一样进行数学运算,并且如果执行了链接时间优化,并且如果库以某种中间形式保存,那么是可以的。

    大多数编译器并没有做到这一切。

    【讨论】:

      【解决方案6】:

      这发生在运行时,因为函数的代码仅在链接步骤(发生在编译步骤之后)期间可用。

      要优化步骤,请使用在使用之前初始化的全局变量。

      【讨论】:

      • 除非它是在包含的标准头文件中声明的标准函数,否则实现确实知道它。
      • 你错了:它知道函数存在,但这并不意味着标准头文件中声明的函数得到任何特殊处理。从编译器的角度来看,它们就像来自任何其他头文件的任何其他函数一样。此外,编译器无法链接,因此无法加载和执行任何标准或非标准函数。
      猜你喜欢
      • 2013-01-23
      • 1970-01-01
      • 1970-01-01
      • 2021-07-12
      • 2013-10-16
      • 2019-05-06
      • 2022-06-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多