1、基础研究
用tcc将程序编译为.obj文件。
这里也可以使用tcc -linclude run.c来将run.c文件编译成run.obj文件。
再用tcc对下面的程序进行编译链接,发现提示错误:
提示标志f在程序中未定义。这说明如果程序里出现未定义的变量或函数,编译器能够正常将原文件编译成.obj文件,只是会提示而已。
要怎么生成正确的exe文件呢,我们要把run1.c中未定义的f函数链接进来,但是我们之前的链接都是链接的系统提供的相关文件,怎么链接自带文件呢?我们先研究一下tlib.exe。
tlib.exe使用方法如下;
即使用tlib的格式为tlib libname [/C] [/E] commands, listfile
libname:要建立的用户目标模块库,缺省的扩展名为.LIB
/C:大小写敏感标志。该选项不常用。
/E:建立扩展字典。建立扩展字典可以加速大的库文件的连接过程。
commands: 操作列表,由若干个动作符以及每个动作符后面的文件名或模块名组成。TILB支持的动作符有5种:“+"、“-"、“*"、“-*"或“*-"、“-+"或“+-"。“+"是把指定的文件加到指定的库中; “-"从库中删除指定的模块;“*"将相应的模块从库中抽取并写到指定的文件中,原库不变;“-*"或“*-"是将库中指定的模块拷贝到指定的文件中,然后把该模块从库中删除;“-+"或“+-"是将指定的模块用指定的文件或模块代替。
Listfile:建立列表文件。列表文件按字母顺序将库中各模块列表,为文本文件,可用DOS的TYPE命令查看。
那么将含有f函数的run.obj添加到cs.lib里的语句如下:
之后用tcc编译链接文件run1.c,没有出现错误提示。用debug加载生成的exe文件:
这是main函数的代码
这是f函数的代码
所以,我们将函数f写在程序run.c中,编译成run.obj,再将run.obj用tlib链接入cs.lib,这样tcc编译时发现原文件中没有定义函数f,就会在c语言默认的函数库cs.lib中寻找,找到后将其链接,生成run1.exe文件,所以我们在run1.exe文件中可以看到函数f的代码,它是在tlink连接时加入的。
用tlib cs.lib cs.txt可以在cs.txt或者cs.lst文件中导入cs.lib的函数的目录。
将此程序编译成f.obj,并加入cs.lib中。
将上面的程序编译链接成b.exe,用debug加载:
Main函数的内容为:
f1函数的内容为:
f2函数的内容为:
函数func的内容为;
程序b.c中并没有写f1、f2和printf函数,这些函数的代码同样是在连接的时候加入的。
b.exe中有f3的代码,因为f3和f1、f2一起被加入了cs.lib中,而cs.lib被连接入了b.exe,所以b.exe含有它的全部函数,只是main函数中只调用了f1、f2、printf函数而已。
函数f3的代码紧接着函数f2的代码,地址为1056.
那么有没有一种方案,使得在编译连接时能够动态地装入函数的代码,而不是将库函数全部装入exe文件呢?
我们看题目要求的是f.c中的三个函数要装入cs.lib,在编译连接时动态载入代码。但是如果我们用tcc原来的连接方式,就会把cs.lib整个载入代码中。那应该怎么样才不会出现这种情况呢?我觉得应该是改变tlink的连接方式,查看tlink连接选项:
我用tcc将b.c编译成obj文件,再用tlink b.obj/n进行连接,结果出现如下错误:
很显然是没有连接cs.lib所致,但是为什么先用tcc编译再用tlink连接会出错呢?
经过查找资料和实验,tlink连接obj文件生成exe文件的正确指令如下:
第一个意思是用小模式连接b.obj,第二个是指生成的目标文件是b.exe,第三个是指没有使用到映像文件,第四个是指连接需要用到的库文件有cs.lib、emu.lib、maths.lib。
函数f3包含在f.obj里,而后者被载入了cs.lib文件,cs.lib在连接时被载入b.exe文件,那么是不是cs.lib里的所有函数都载入了b.exe文件呢?查找资料有这么一段话:
不会。当启动连接程序时,它会寻找“未定义的外部函数”,也就是说,它将在每一个库文件中查找源代码文件中未定义的函数。当它找到一个未定义的外部函数后,它会引入包含该函数定义的目标代码(obj)。不幸的是,如果这个函数是在一个包含其它函数定义的源文件中被编译的话,那么这些函数也会被包含进来,你的可执行代码中将包含一些不需要的代码。因此,将库函数放到各自的源文件中是很重要的——否则会浪费宝贵的程序空间。有些编译程序包含特殊的“精明的”连接程序,这些连接程序能查出不需要的函数并去掉它们,从而使这些函数不再进入你的程序。
很显然tlink并不是上面资料里所说的“精明的”连接程序,它会将和源代码中未定义函数一起编译的所有函数都载入exe文件中,所以f3会被载入b.exe中。
那么我们的问题就很好解决了,我们可以将f1、f2函数写在一个程序里编译成obj文件,将f3写在一个程序里编译成obj文件,再将这两个文件加入cs.lib中,然后进行下面的连接,b.exe文件中就不会出现f3函数的代码了。实验结果发现,原来紧跟在f2的代码后面的f3的代码现在没有了:
所以其实是我之前的思路重点错了,只觉得函数f1、f2、f3都在cs.lib里面不管怎么放都是一样的,但是其实它们编译的方法不同,在cs.lib里面存放的位置或机制也应该不同,我们思考时,应该更加全面地想问题。
用下面的函数替换cs.lib里的printf函数:
将函数编译成obj文件,再用如下语句替换:
这样再使用printf函数就会输出“Do you want to use printf?No printf here.”
如下图程序:
在正常情况下应该输出3,但是用更改后的cs.lib连接后,输出结果如下:
这时printf函数是一个不接受参数、只输出固定语句的函数。
2、拓展研究
1、我们在本章研究里是把要添加的obj文件插入cs.lib中再连接,那么能否自己建立lib库,并使tcc编译时对它进行连接呢?
2、我们知道cs.lib里在被连接时是将源文件中未被定义的函数及其一起编译的函数全部加入生成的exe文件中,那么cs.lib里的其他文件是怎么编译的?都是单独编译的吗?
3、Printf和put函数有什么区别?
4、obj文件给出的是偏移地址,exe文件给出了段地址和偏移地址?
5、include头文件在预编译过程中把其他文件合成一个文件。尝试#include<f.c>也是可以正确运行的,看汇编代码这种和加入cs.lib有什么不一样。
6、加入obj文件后之后cs.lib的大小减小了,这是为什么?
3、研究总结
本章研究了obj文件的连接问题,掌握了将obj文件加入cs.lib从而连接进文件的方法,熟悉了tlib.exe的基本用法。