为了方便和连贯,我倾向于使用常量表达式的数学函数(即log(x)/log(2)
,而不是log(x)/0.3...
)。由于这些函数实际上并不是语言本身的一部分,因此它们也没有定义math.h
(仅声明),常量函数会在编译时预先计算,还是会在运行时被浪费地计算?
6 回答
这取决于编译器和优化标志。正如@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 编译器中都有相同的行为,因为这是一个微不足道的优化。
一些编译器会在编译时评估它们,但不能保证这种行为(这样做也可能导致问题)。你需要检查你的编译器,看看它做了什么。
请注意,在许多系统上,可log(2)
从M_LN2
.<math.h>
它们通常会在运行时进行计算(有关如何内联它们,请参阅其他答案),尽管我不一定会使用“浪费”这个词,除非它们很多和/或代码被执行了很多次。
与其只是放入常量值,不如创建一个#define
或const
变量来表达该值的含义(PI
、LOG_2
等)并使用它来代替。
例如:
#define LOG_2 0.30102999566
这不会进行任何计算,您可以将值指定为所需的精度或在给定您的环境(32 位 v 64 位)下可以管理的精度。
这取决于。如果编译器可以像在运行时一样进行数学运算,并且如果执行了链接时间优化,并且如果库以某种中间形式保存,那么是的,它可以完成。
大多数编译器并没有做到这一切。
在库函数的情况下,一些编译器可能将这些函数实现为内在函数,这意味着编译器对函数有足够的了解,可以在编译时用常量替换调用。是否会这样做取决于编译器。在实践中,我经常注意到一些编译器不愿意在编译时预先计算浮点表达式,即使它们不涉及任何函数调用。
在一般情况下,通常它们不会也无法在编译时计算,假设编译器根本不了解这些函数,无法在编译时计算它们。也许他们有一些突出的运行时副作用?
一些编译器可能具有非标准的依赖于编译器的扩展,允许用户向编译器提供有关函数的附加信息,以便编译器可以更好地优化函数调用,甚至确定是否可以用编译器替换给定的调用。时间预计算。例如,GCC 编译器支持这样的函数属性(GCC 特定的扩展)const
和pure
. const
如果参数在编译时是已知的,那么理论上可以用编译时预计算来替换具有该属性的函数调用。虽然我不能说 GCC 是否真的可以做到这一点。
在 C++(即使您的问题被标记为 C)语言中,一个计划中的新功能是一个constexpr
声明说明符,它具有类似的目的并且应该具有您描述的效果。
这发生在运行时,因为函数的代码仅在链接步骤期间可用(发生在编译步骤之后)。
要优化该步骤,请使用在使用之前初始化的全局变量。