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