在gsl中,由于有float、double、long double多种数据精度,因此如果针对每一种数据精度都分别实现诸如内存分配等函数,代码量将骤增数倍。因此各种数据类型共用这些代码就显得相当重要。gsl利用c语言的预编译命令实现了这种代码的重用功能。
这里我们以最为基础的gsl_block_alloc函数模板为例,剖析gsl代码重用的原理。
下面给出该函数的模板源代码
【block/init_souce.c】 ...... TYPE (gsl_block) * FUNCTION (gsl_block, alloc) (const size_t n) { TYPE (gsl_block) * b; ...... b = (TYPE (gsl_block) *) malloc (sizeof (TYPE (gsl_block))); ...... b->data = (ATOMIC *) malloc (MULTIPLICITY * n * sizeof (ATOMIC)); ...... b->size = n; return b; }
在《gsl数据类型之向量http://blog.csdn.net/insectC/archive/2010/01/27/5262444.aspx》一文中,我们指出对于double精度,函数中宏的值为:
【templates_on.h】 ...... #define ATOMIC double #define MULTIPLICITY 1 ...... #define CONCAT2x(a,b) a ## _ ## b #define CONCAT2(a,b) CONCAT2x(a,b) ...... #define FUNCTION(dir,name) CONCAT2(dir,name) #define TYPE(dir) dir ......
编译后将得到gsl_block_alloc函数,其作用是创建一个包含n个double精度数据的数据块。
而实际上,这一函数模板同样也可以编译成gsl_block_float_alloc,gsl_block_long_double_alloc等等,分别用于创建float精度与long double精度的向量。
其中的奥秘就在block/init_source.c与templates_on.h、templates_off.h的互相配合中。让我们来看看block/init.c文件的代码:
【block/init.c】 #include <config.h> #include <stdlib.h> #include <gsl/gsl_block.h> ...... #define BASE_DOUBLE #include "templates_on.h" #include "init_source.c" #include "templates_off.h" #undef BASE_DOUBLE #define BASE_FLOAT #include "templates_on.h" #include "init_source.c" #include "templates_off.h" #undef BASE_FLOAT ......
限于篇幅,其中略去了除float,double外的其他精度类型的代码。
让我们来跟踪编译过程。首先在定义了BASE_DOUBLE后,编译器将引入templates_on.h。头文件中的关键内容如下:
【templates_on.h】 #if defined(BASE_GSL_COMPLEX_LONG) ...... #elif defined(BASE_DOUBLE) #define BASE double #define SHORT #define ATOMIC double #define MULTIPLICITY 1 #define FP 1 #define IN_FORMAT "%lg" #define OUT_FORMAT "%g" #define ATOMIC_IO ATOMIC #define ZERO 0.0 #define ONE 1.0 #define BASE_EPSILON GSL_DBL_EPSILON #elif defined(BASE_FLOAT) #define BASE float #define SHORT float #define ATOMIC float #define MULTIPLICITY 1 #define FP 1 #define IN_FORMAT "%g" #define OUT_FORMAT "%g" #define ATOMIC_IO ATOMIC #define ZERO 0.0F #define ONE 1.0F #define BASE_EPSILON GSL_FLT_EPSILON ...... #endif
在文件中,ATOMIC被定义为 double,MULTIPLICITY被定义为1。这与前文一致。随后我们又将进一步定义一系列的宏:
【templates_on.h】 ...... #define CONCAT2x(a,b) a ## _ ## b #define CONCAT2(a,b) CONCAT2x(a,b) #define CONCAT3x(a,b,c) a ## _ ## b ## _ ## c #define CONCAT3(a,b,c) CONCAT3x(a,b,c) #define CONCAT4x(a,b,c,d) a ## _ ## b ## _ ## c ## _ ## d #define CONCAT4(a,b,c,d) CONCAT4x(a,b,c,d) ...... #if defined(BASE_DOUBLE) #define FUNCTION(dir,name) CONCAT2(dir,name) #define TYPE(dir) dir #define VIEW(dir,name) CONCAT2(dir,name) #define QUALIFIED_TYPE(dir) TYPE(dir) #define QUALIFIED_VIEW(dir,name) CONCAT2(dir,name) #else #define FUNCTION(a,c) CONCAT3(a,SHORT,c) #define TYPE(dir) CONCAT2(dir,SHORT) #define VIEW(dir,name) CONCAT3(dir,SHORT,name) #define QUALIFIED_TYPE(dir) TYPE(dir) #define QUALIFIED_VIEW(dir,name) CONCAT3(dir,SHORT,name) #endif
在这里,FUNCTION(dir,name)宏被定义为CONCAT2(dir,name),而TYPE(dir)宏被定义为dir。
这样,当我们重新回到init.c并进入view_source.c编译时,便顺理成章的得到了gsl_block_alloc等一系列double精度的函数。
现在,编译器只剩下一点扫尾工作,即进入templates_off.h文件,关闭已有宏定义,为下一步float精度的编译做好准备。这里节选代码如下:
【templates_off.h】 #ifdef FUNCTION #undef FUNCTION #endif #ifdef CONCAT4 #undef CONCAT4 #endif #ifdef CONCAT4x #undef CONCAT4x #endif #ifdef CONCAT3 #undef CONCAT3 #endif #ifdef CONCAT3x #undef CONCAT3x #endif #ifdef CONCAT2 #undef CONCAT2 #endif #ifdef CONCAT2x #undef CONCAT2x #endif #ifdef TYPE #undef TYPE #endif ...... #undef BASE #undef BASE_EPSILON #undef SHORT #undef ATOMIC #undef MULTIPLICITY #undef IN_FORMAT #undef OUT_FORMAT #undef ATOMIC_IO #undef ZERO #undef ONE ...... #ifdef FP #undef FP #endif
这样double精度的函数编译工作便完成了。按照init.c中的流程,编译器将定义宏BASE_FLOAT,并开始float精度函数的编译。读者可以自己从上面的代码中体会其中的精妙。