在定义宏中是否优化任何C或C ++编译器?

时间:2011-07-22 21:30:56

标签: c++ c c-preprocessor compiler-optimization

假设我在 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)))

然后,我去许多嵌套循环深处使用NODESA_CONSTANT(即多次使用)。显然,两者都有可以在编译时确定的数值,但编译器实际上是这样做的吗?在运行时,CPU每次看到15*16时都必须评估NODES,还是编译器静态放置240?同样,每次看到A_CONSTANT时,CPU都必须评估平方根吗?

我的猜测是ROWS*COLS乘法被优化了,但没有别的。整数乘法内置于语言中,但sqrt是一个库函数。确实如此,是否有任何方法可以获得相当于A_CONSTANT的幻数,以便在运行时仅对平方根进行一次评估?

6 个答案:

答案 0 :(得分:20)

宏定义通过简单的文本替换扩展到源代码中,然后再转交给编译器,这可能会进行优化。编译器将为表达式NODESROWS*COLS15*16生成完全相同的代码(我不能想到每次循环时都会进行乘法运算的单个代码已启用优化)。

至于A_CONSTANT,它再次成为一个宏的事实并不重要;重要的是编译器是否足够聪明,以确定常量的sqrt是常量(假设来自sqrt的{​​{1}})。我知道GCC足够聪明,我希望其他生产质量的编译器也足够聪明。

答案 1 :(得分:11)

#define中的任何内容都作为预编译步骤插入到源代码中,这意味着一旦编译代码,宏基本上就消失了,代码就像往常一样编译。是否优化取决于您的代码,编译器和编译器设置。

答案 2 :(得分:9)

这取决于你的编译器。

#include <math.h>

#define FOO sqrt(5);

double
foo()
{
  return FOO;
}

我的编译器(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:

所以它确实知道sqrt(5)是一个编译时常量。

如果您的编译器不那么聪明,我不知道在编译时计算平方根的任何可移植方法。 (当然,您可以计算一次结果并将其存储在全局或其他内容中,但这与编译时常量不同。)

答案 3 :(得分:8)

这里真的有两个问题:

  1. 编译器是否优化了宏中的表达式?
  2. 编译器是否优化sqrt()
  3. (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);
       }
    }
    

    编译器实际上会看到:

    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);
       }
    }
    

    这受所有常规编译时常量优化的影响。

    sqrt()比较复杂,因为它从编译器到编译器各不相同。在大多数现代编译器中,sqrt()实际上是compiler intrinsic而不是库函数 - 它看起来像一个函数调用,但它实际上是编译器中的一个特殊情况,它具有基于数学定律,硬件操作的额外启发式算法在sqrt()是如此特殊情况的智能编译器中,常量值的sqrt()将在内部转换为常数。在愚蠢的编译器中,每次都会产生一个函数调用。知道你得到的唯一方法是编译代码并查看发出的程序集。

    从我所看到的,MSVC,现代GCC,英特尔,IBM和SN都将sqrt作为内部处理。旧的GCC和一些供应商提供的用于嵌入式芯片的糟糕的编译器没有。

答案 4 :(得分:3)

在编译之前

#defines处理,只需简单的文本替换。然后将生成的文本文件传递给实际的编译步骤。

如果您正在使用gcc,请尝试使用-E开关编译源文件,该文件将执行预处理然后停止。查看生成的文件以查看编译步骤的实际输入。

答案 5 :(得分:1)

宏将被替换,然后代码编译就像其余的代码一样。如果你已经启用了优化(并且你正在使用的编译器进行正常优化),你可能希望在编译时计算这样的事情。

从正确的角度来看,相对较少的C ++编译器已经足够老了,你会期望它们缺乏这样的优化。编程器足够老,缺乏简单的优化通常只有C(即便如此,不要依赖它 - 绝对像MS C 5.0 / 5.1 / 6.0,Datalight / Zortech C,Borland等,这样做了从我记得的情况来看,在CP / M上运行的C编译器大多没有。