msh*_*ang 9 c c++ compiler-optimization c-preprocessor
假设我在C或C++中有以下内容:
#include <math.h>
#define ROWS 15
#define COLS 16
#define COEFF 0.15
#define NODES (ROWS*COLS)
#define A_CONSTANT (COEFF*(sqrt(NODES)))
Run Code Online (Sandbox Code Playgroud)
然后,我去使用NODES
和A_CONSTANT
内许多嵌套循环深的地方(即多次使用).显然,两者都有可以在编译时确定的数值,但编译器实际上是这样做的吗?在运行时,CPU是否必须在15*16
每次看到时进行评估NODES
,还是编译器静态放置240
?同样,CPU每次看到时都必须评估平方根A_CONSTANT
吗?
我的猜测是ROWS*COLS
乘法被优化了,但没有别的.整数乘法内置于语言中,但sqrt是一个库函数.如果确实是这种情况,有没有办法得到一个等效的幻数,以便A_CONSTANT
在运行时只计算一次平方根?
Fre*_*Foo 20
宏定义通过简单的文本替换扩展到源代码中,然后再转交给编译器,这可能会进行优化.编译器将为表达式生成完全相同的代码NODES
,ROWS*COLS
并且15*16
(我无法想到每次循环启用优化时都会进行乘法的单个代码).
至于A_CONSTANT
它再次成为一个宏的事实并不重要; 重要的是编译器是否足够聪明,以确定sqrt
常量是一个常量(假设是sqrt
来自<math.h>
).我知道GCC足够聪明,我希望其他生产质量的编译器也足够聪明.
这取决于你的编译器.
#include <math.h>
#define FOO sqrt(5);
double
foo()
{
return FOO;
}
Run Code Online (Sandbox Code Playgroud)
我的编译器(gcc 4.1.2)为此代码生成以下程序集:
.LC0:
.long 2610427048
.long 1073865591
.text
.p2align 4,,15
.globl foo
.type foo, @function
foo:
.LFB2:
movsd .LC0(%rip), %xmm0
ret
.LFE2:
Run Code Online (Sandbox Code Playgroud)
所以它确实知道这sqrt(5)
是一个编译时常量.
如果您的编译器不那么聪明,我不知道在编译时计算平方根的任何可移植方法.(当然,您可以计算一次结果并将其存储在全局或其他内容中,但这与编译时常量不同.)
这里真的有两个问题:
sqrt()
?(1)很容易:是的,确实如此.预处理器与C编译器分开,并在C编译器启动之前完成它.所以,如果你有
#define ROWS 15
#define COLS 16
#define NODES (ROWS*COLS)
void foo( )
{
int data[ROWS][COLS];
printf( "I have %d pieces of data\n", NODES );
for ( int *i = data; i < data + NODES ; ++i )
{
printf("%d ", *i);
}
}
Run Code Online (Sandbox Code Playgroud)
编译器实际上会看到:
void foo( )
{
int data[15][16];
printf( "I have %d pieces of data\n", (15*16) );
for ( int *i = data; i < data + (15*16) ; ++i )
{
printf("%d ", *i);
}
}
Run Code Online (Sandbox Code Playgroud)
这受到所有通常的编译时常量优化的影响.
sqrt()
更棘手,因为它从编译器到编译器各不相同.在大多数现代编译器中,sqrt()实际上是编译器内部函数而不是库函数 - 它看起来像一个函数调用,但它实际上是编译器中的一个特殊情况,它具有基于数学定律,硬件操作等的附加启发式算法.在sqrt()
这种特殊情况下的智能编译器中,sqrt()
常量值将在内部转换为常数.在愚蠢的编译器中,每次都会产生一个函数调用.知道你得到的唯一方法是编译代码并查看发出的程序集.
从我所看到的情况来看,MSVC,现代GCC,英特尔,IBM和SN都是sqrt
内置的.旧的GCC和一些蹩脚的供应商提供的嵌入式芯片编译器没有.