目录
第1章 VC++
1.1 inline与__inline
对于VC++而言,inline仅用于内联C++函数,而__inline可用于内联C和C++函数。
1.2 启用内联
编译VC++ Debug 版程序时,内联功能默认是被禁用的。如果需要,可以启用内联。以VC++6.0为例,其操作步骤如下:
将Inline function expansion由Disable*更改为Only __inline。如下图所示:
图1.1
将Debug info由Program Database for Edit and Continue更改为Program Database。如下图所示:
图1.2
1.3 内联和外联
查看如下代码:
#include <STDIO.H>
inline void FuncInline() { printf("FuncInline "); }
void Func1() { FuncInline(); } |
VC++编译Release版(内联被启用),Func1里的代码FuncInline();将被展开为printf("FuncInline ");此即为FuncInline的内联版本。
VC++编译Debug版(内联被禁用),Func1里的代码FuncInline();将调用函数FuncInline,此即为FuncInline的外联版本。
使用UltraEdit打开Release版和Debug版生成的obj文件,就会发现:Debug版的obj文件里能查找到字符串FuncInline,而Release版的obj文件里就查不到字符串FuncInline。这说明:在启用内联功能的情况下,编译器一般不会生成内联函数的外联代码。但也有特殊情况,如下面的代码:
#include <STDIO.H>
inline void FuncInline() { printf("FuncInline "); }
void Func1() { void(*pFunc)() = &FuncInline; (*pFunc)(); } |
Func1里首先获得函数FuncInline的指针,然后再调用此函数。此时,即使启用了内联功能也必须生成FuncInline的外联代码,且pFunc指向的是外联代码,(*pFunc)();执行的是FuncInline的外联版本。
1.3.1 何时使用内联
参考下面的代码:
#include <STDIO.H>
inline void FuncInline() { printf("FuncInline "); }
void Func1() { FuncInline(); } |
只有内联功能被启用,且FuncInline能够被内联的时候,Func1里的FuncInline();才会使用内联代码。
若内联功能被禁用(如:编译Debug版)或FuncInline太复杂而无法内联时,Func1里的FuncInline();将使用外联版本,其实就是函数调用。
1.3.2 何时使用外联
1、通过函数指针调用函数,则一定使用外联版本。
2、只有函数声明,没有函数实现的内联函数,一定使用外联版本。如下面的代码:
inline void FuncInline();
void Func() { FuncInline(); } |
虽然FuncInline被声明为内联函数,但是这个编译单元(编译前是c或cpp文件,编译后就是obj文件)没有该函数的内联代码,因此Func里的FuncInline();使用的是外联版本。
1.3.3 外联单选
假定一个VC++项目包含如下源文件:
1.cpp
#include <STDIO.H>
inline void FuncInline() { printf("inline in 1.cpp "); }
void Func1() { void(*pFunc)() = &FuncInline; FuncInline(); printf("Func1 %p ",pFunc); (*pFunc)(); } |
2.cpp
#include <STDIO.H>
inline void FuncInline() { printf("inline in 2.cpp "); }
void Func2() { void(*pFunc)() = &FuncInline; FuncInline(); printf("Func2 %p ",pFunc); (*pFunc)(); } |
VC++编译Debug版(内联被禁用),此时1.obj和2.obj均有FuncInline的外联版本。Func1和Func2里的代码FuncInline();究竟会调用哪个obj文件里的FuncInline?答案是连接时的第一个,其它的均被舍弃。此时,Func1和Func2里的pFunc是相等的。
注意:VC++只能单选inline函数,不能单选外部函数。假定有如下源代码文件:
3.cpp
#include <STDIO.H>
void FuncInline() { printf("FuncInline in 3.cpp "); }
void Func3() { void(*pFunc)() = &FuncInline; FuncInline(); printf("Func3 %p ",pFunc); (*pFunc)(); } |
VC++编译Debug版(内联被禁用)时,3.obj里将有外部函数FuncInline的二进制代码。此时将有三个FuncInline:一个是3.obj里的外部函数;另两个是1.obj、2.obj里的外联函数。外联函数可以单选,但是外联函数与外部函数是不能单选的。这就造成连接时编译器无法确定Func1、Func2、Func3究竟要使用外联函数还是外部函数,从而导致编译失败。
1.3.4 外联并存
使用static inline可以保留一个内联函数的所有外联版本,其功能为:修饰内联函数的外联版本为static函数。
举例说明:现在修改1.cpp和2.cpp的inline为static inline。
VC++编译Debug版(内联被禁用),此时1.obj和2.obj均有FuncInline的外联版本,但是它们都是static函数。因此,相同的代码FuncInline();,在Func1里会调用1.obj里的FuncInline,在Func2里会调用2.obj里的FuncInline。同样的,Func1和Func2里的pFunc也分别指向1.obj和2.obj里的FuncInline。
显然:外联单选时,不会单选static inline函数的外联版本。
1.3.5 extern inline
根据static inline可以理解extern inline的功能为:修饰内联函数的外联版本为extern函数(即外部函数)。事实上,不使用extern关键字,外联函数就是外部函数。因此,VC++里extern inline与inline是没有任何分别的。
1.4 动态库
1.4.1 导出内联函数
VC++编译生成动态链接库时,允许导出内联函数。要点就是使用__declspec(dllexport),如:
1.cpp
__declspec(dllexport) __inline int Test() { return 1; } |
2.cpp
__declspec(dllexport) __inline int Test() { return 2; } |
说明:
1、导出的都是内联函数的外联版本,导出前会外联单选。所以上面代码导出的Test函数有可能返回1也有可能返回2;
2、仅仅在模块定义文件(*.DEF)里导出内联函数是不够的,必须使用__declspec(dllexport)。
1.4.2 导入内联函数
根据1.cpp和2.cpp生成的Test.dll将导出函数Test。客户程序可以调用此函数,如下所示:
#pragma comment(lib,"Test.lib")
__declspec(dllimport) __inline int Test() { return 3; }
int main() { Test(); int(*pFunc)() = &Test; (*pFunc)(); return 0; } |
这里Test函数有两个版本:内联版本——返回3的版本;导入版本——该程序导入的Test.dll内的Test函数。
__declspec(dllimport)有两个作用:
1、禁止内联函数生成外联代码;
2、需要外联代码的时候使用内联函数的导入版本。
现在分析上述代码的行为:
1、内联被启用时,main函数里的Test();调用的是内联版本;
2、内联被禁用时,main函数里的Test();调用的是外联版本,即导入版本;
3、不论是否启用了内联功能,(*pFunc)();始终调用外联版本,即导入版本。
1.5 静态库
1.5.1 交叉调用外联函数
所谓交叉调用就是:客户程序调用静态库的外联函数,或静态库调用客户程序的外联函数。
如:程序员在不知道静态库里有内联函数FuncInline的情况下,在客户程序里也编写了内联函数FuncInline。当静态库、客户程序均不启用内联功能时,就会存在外联单选的问题。单选结果就是选用静态库的外联版本或选用客户程序的外联版本,此时客户程序调用此函数或静态库调用此函数就会发生交叉调用现象。
只要静态库、客户程序的内联函数的参数、实现保持一致,交叉调用并不可怕。但是,因为静态库的源代码程序员可能是看不到的,因此由交叉调用而产生的BUG是无法绝对避免的。
1.5.2 缺少外联函数
GSL函数库里有函数gsl_complex_rect的声明及实现代码,如下所示:
INLINE_DECL gsl_complex gsl_complex_rect (double x, double y); |
#ifdef HAVE_INLINE INLINE_FUN gsl_complex gsl_complex_rect (double x, double y) {/* return z = x + i y */ gsl_complex z; GSL_SET_COMPLEX (&z, x, y); return z; } #endif |
这个函数很简单,就是返回复数。
假定生成GSL静态库时,定义了宏HAVE_INLINE,即编译器支持内联功能,则gsl_complex_rect的声明及实现代码如下:
__inline gsl_complex gsl_complex_rect (double x, double y); |
__inline gsl_complex gsl_complex_rect (double x, double y) {/* return z = x + i y */ gsl_complex z; GSL_SET_COMPLEX (&z, x, y); return z; } |
在启用内联功能的前提下编译GSL,生成的静态库里将不会有gsl_complex_rect的外联代码。
假定调用GSL的客户程序里没有定义宏HAVE_INLINE,则在客户程序看来gsl_complex_rect的实现代码将被禁用,同时其声明代码如下:
gsl_complex gsl_complex_rect (double x, double y); |
假定客户程序里调用了函数gsl_complex_rect,那么在连接客户程序时需要查找gsl_complex_rect的二进制代码。可惜客户程序、GSL静态库里均找不到,结果就是连接错误。