C中的条件编译用于获取一个函数的不同版本

时间:2015-06-14 09:42:51

标签: c c-preprocessor conditional-compilation

我问自己是否有一种很好的方法可以在不复制整个源代码的情况下获得一个函数的不同版本。我希望有一个用于测量执行时间的不同版本,另一方面用于编写一些中间结果(用于分析目的)。

让我通过这个例子解释一下: 我正在编写一个用于求解线性系统的迭代求解器,在我的例子中是GMRES算法。它希望在一次运行中测量运行时,并在不同运行中的每个迭代步骤中写入残差。我的程序结构如下:

//main.c

#include "gmres.h"

// setup arrays
flag_print_res = 0;
GMRES(A,x,b,tol,maxtiter,flag_print_res);
// reset arrays
flag_print_res = 1;
GMRES(A,x,b,tol,maxtiter,flag_print_res);
//..

和功能:

//GMRES.c

void GMRES(double* A, double *x, double *b, double tol, int maxiter, int flag_print_res)
{
    // …
    start_time = ((double) clock ())/CLOCKS_PER_SEC;
    for(iter=0; iter<maxiter; iter++)
    {
        // ...
        if(flag_print_res)
        {
            fprintf(file, "%d %13.5e\n", iter, res);
        }
        // ...
    }
    end_time = ((double) clock ())/CLOCKS_PER_SEC;
    printf("execution time = %13.5e\n", end_time-start_time);
}

然而,执行时间的问题是,评估if语句会花费时间,特别是在每次迭代时评估它。因此我的问题是:如何创建函数的不同版本,在编译时评估if语句?

3 个答案:

答案 0 :(得分:1)

使用指令。使用指令,您可以指示C预处理器打印或不打印。

通过这种方式进行调试是一种常见的做法。在编译时,您只能做出一个决定。 (你看下面我把#define#undef放在同一个源文件中。决定是在编译时,所以你可以使用其中一个。我希望你能得到这个想法。你需要如果要执行程序的两个版本,请编译两次。)

调用程序时,可以将指令作为参数添加到(gcc?)编译器中,这样每次要切换指令时都不需要修改源代码(例如gcc -D FLAG_PRINT_RES .... )。下面是两种可能性。

//main.c

#include "gmres.h"

// setup arrays

================
#define FLAG_PRINT_RES;  <-- flag is set. when you compile the code fprintf below is compiled
GMRES(A,x,b,tol,maxtiter);
=======OR=======
#undef FLAG_PRINT_RES; <-- flag is not set. when you compile the code fprintf below is not compiled
GMRES(A,x,b,tol,maxtiter);
================

// reset arrays
//..

并在定义时使用该标志。

//GMRES.c

void GMRES(double* A, double *x, double *b, double tol, int maxiter)
{
    // …
    start_time = ((double) clock ())/CLOCKS_PER_SEC;
    for(iter=0; iter<maxiter; iter++)
    {
        // ...
        #ifdef FLAG_PRINT_RES <-- if flag is set then compile the next line of code
            fprintf(file, "%d %13.5e\n", iter, res);
        #endif
        // ...
    }
    end_time = ((double) clock ())/CLOCKS_PER_SEC;
    printf("execution time = %13.5e\n", end_time-start_time);
}

请注意,它是预处理器(因此在编译时决定之前)。在不使用函数指针的情况下,你不能在C语言的运行时使用动态方法,至少不是我所知道的。

如果你使用C ++,你可能会找到一个运行时解决方案(你可以重载方法/函数)。

如果允许或多或少地更改代码,您可以使用函数指针在C中找到解决方案。但是,设置一个示例对我来说太费劲了,我将在下面的附录中向您展示基础知识。我不确定它是否比if语句更好,因为每次调用一个函数时,都会在返回后构建并销毁一个上下文。但是,如果添加更多的print语句,那么函数指针可能是一个可行的解决方案。

所以这是摘要:

  • 只需留下if声明
  • 即可
  • 使用预处理器有条件地进行编译
  • 使用函数指针决定是否打印

附录(功能指针)

#include <stdio.h>

int (*p)(const char *format, ...);                           <-- function pointer to printf(...) library function
int p_null(const char *format, ...);                         <-- function of identical signature to printf, but doing nothing
void f(int (*p)(const char *format, ...), const char *text); <-- a function receiving a function pointer and optionally other parameters

int main() {

    p = &printf;  <-- point to printf library function
    f(p, "FLAG"); <-- will print FLAG in console
    p = &p_null;  <-- point to dummy function doing nothing
    f(p, "FLAG"); <-- will not print FLAG in console

    return 0;
}

// dummy function doing nothing
int p_null(const char *format, ...) {;}

// function call receiving function pointer
void f(int (*p)(const char *format, ...), const char *text) {
    (*p)("TEST");
}

答案 1 :(得分:1)

您可以使用宏在编译时控制设置。但是,它们通常只用于构建一个版本的函数。

如果要在同一程序中同时提供这两个版本,则必须实现该功能的两个不同版本。预处理器可以帮助您避免重复代码。

多次包含单独的实现

一种方法是在单独的文件gmres.c中实现该功能:

#ifndef GMRES
#error "GMRES not defined"
#endif

void GMRES(double* A, double *x, double *b, double tol, int maxiter)
{
    for (iter=0; iter < maxiter; iter++)
    {
        // ...

        #ifdef GMRES_PRINT
        fprintf(file, "%d %13.5e\n", iter, res);
        #endif

        // ...
    }
}

然后将该文件包含两组宏:

#define GMRES gmres            // plain version
#include "gmres.c"
#undef GMRES

#define GMRES gmres_print     // printing version
#define GMRES_PRINT           // set print flag
#include "gmres.c"
#undef GMRES

虽然#include通常用于包含头文件,但它可以用于包含任何文件,这里包含*.c文件是有意义的。确保您没有#pragma once或类似的设置处于活动状态。 #error指令确保函数的名称以宏的形式给出。

编译单独的对象

通过将相同的源文件编译为两个单独的对象,可以实现同样的目的:

cc -DGMRES=gmres -o gmres.o -c gmres.c
cc -DGMRES_PRINT -DGMRES=gmres_print -o gmres_print.o -c gmres.c

cc -o gmres gmres.o gmres_print.o ...

这里,通过构建控制工具控制版本,例如, Makefile文件。您必须确保没有重复的符号。

便宜的模板

另一种方法是&#34;便宜的模板&#34;将整个函数实现为宏:

#define GMRES_IMPL(NAME, FLAG)                                       \
void NAME(double* A, double *x, double *b, double tol, int maxiter)  \
{                                                                    \
    for (iter=0; iter < maxiter; iter++)                             \
    {                                                                \
        // ...                                                       \
                                                                     \
        if (FLAG) fprintf(file, "%d %13.5e\n", iter, res);           \
                                                                     \
        // ...                                                       \
    }                                                                \
}

然后使用不同的参数实现它的两个版本:

GMRES_IMPL(gmres, 0)
GMRES_IMPL(gmres_print, 1)

您不能在宏中使用预处理程序指令,因此必须通过宏参数进行控制。编译器可以评估if (0)if (1)等常量表达式,并相应地消除死代码。

这种方法可能有其用途,但有一个主要缺点:所有错误消息和调试符号都是指使用GMRES_IMPL引发函数实现的行,这使得查找错误非常困难。

答案 2 :(得分:0)

你可以这样做,使flag_print_res是一个在编译时已知的宏。如果编译器在编译时知道if的条件为false,并且启用了优化,那么它将删除if(如果它是一个好的编译器)。

然后你可以在makefile中创建两个版本的程序,一个版本使用define,另一个版本使用define false,所以一个程序测量运行时间,另一个程序测量运行时间。