1.6.3 目标属性
前面提到源程序的属性可以用于确定调用的编译器及传递的参数,构建目标也应当拥有一些属性。对于伪构建目标而言,属性主要用于表示它应该被如何使用,即确定使用者的编译和链接参数;对于二进制构建目标来说,属性不仅用于表示它应该被如何使用,还用于确定自身源程序编译和链接时所需的参数。
构建要求和使用要求
与构建目标自身源程序相关的属性,确定了构建目标的构建要求(build specification);而与其使用者相关的属性,则决定了构建目标的使用要求(usage requirements)。目标的使用要求,实际上会被传递到该目标使用者的构建要求中。正是这两种需求赋予了构建目标这个概念丰富的内涵,使其称为最核心的抽象概念。
构建要求和使用要求的区别在于要求所作用的对象,其要求本身并无区别——这也很好理解,毕竟这要求最终体现在源程序的编译和链接上,不论作用于谁,这一点都不会有变化。因此,常见的要求也就是之前提到的那些:
● 头文件搜索目录;
● 链接库文件搜索目录;
● 宏定义;
● 其他编译链接参数等。
下面以构建动态库为例,带领大家大致感受一下构建要求和使用要求,二者之间又有何种联系。
Windows中动态库构建目标的要求
回顾代码清单1.19的Makefile中构建动态库的具体命令。其中,最后两条规则是与构建动态库相关的规则,构建要求自然也应该在这里体现,如代码清单1.28所示。
代码清单1.28 ch001/动态库/NMakefile(第7行~第11行)
liba.lib liba.dll: a.obj liba.def
cl a.obj /link /dll /out:liba.dll /def:liba.def
a.obj: a.c
cl /c a.c /Fo"a.obj"
编译构建目标的源程序a.c到目标文件a.obj,并通过/link、/dll、/out:liba.dll和/def:liba.def等参数链接当前构建目标所对应的目标文件a.obj——这就是liba.dll这个动态库构建目标的构建要求。
使用要求自然应该在使用动态库的主程序的构建规则中体现,如代码清单1.29所示。
代码清单1.29 ch001/动态库/NMakefile(第1行~第5行)
main.exe: main.obj liba.lib
cl main.obj liba.lib /Fe"main.exe"
main.obj: main.c
cl -c main.c /Fo"main.obj"
指定liba.lib导入库文件名作为链接参数,就是liba.dll动态库构建目标的使用要求。main.exe作为一个该动态库的使用者,会将liba.lib与主程序编译后的目标文件main.obj一同链接。这里也体现了动态库构建目标的使用要求会被传递给主程序,作为主程序构建要求的一部分。图1.4应该能够更直观地展示这一点。
这种构建要求和使用要求的模型能够将各部分的构建解耦。编写主程序时无须操心它所链接的各个库应当如何被构建和使用,各个库会主动告知这一切。
图1.4 Windows中动态库目标要求示意图
Linux中动态库构建目标的要求
在Linux中构建动态库的Makefile参见代码清单1.20。这里关注最后两条规则,如代码清单1.30所示。
代码清单1.30 ch001/动态库/Makefile0(第7行~第11行)
liba.so: a.o
gcc -shared a.o -o liba.so
a.o: a.c
gcc -fPIC -c .a.c -o a.o
这两条规则实际上声明了动态库liba.so这个构建目标的构建要求:使用-fPIC参数编译构建目标的源程序a.c到目标文件a.o,使用-shared参数将目标文件链接成最终的动态库。
再来看第一条构建主程序的规则,如代码清单1.31所示。
代码清单1.31 ch001/动态库/Makefile0(第1行~第5行)
main: main.o liba.so
gcc main.o -o main -L. -la
main.o: main.c
gcc -c main.c -o main.o
主程序的构建要求包括在链接过程中通过-L.参数指定链接库搜索目录,并通过-la参数指定链接库的名称。这同时也是动态库构建目标的使用要求。正如图1.5所示,动态库的使用要求会传递到主程序的构建要求中。
我们通过重温动态库在不同平台的构建和使用过程,了解了动态库在对应平台的构建要求和使用要求。其他二进制构建目标类型(如静态库、可执行文件)与之类似,但伪构建目标会有些不同,因为它们不需要被构建,自然也就不存在对应的构建要求,而只存在使用要求。
图1.5 Linux中动态库目标要求示意图