【永利皇宫】深入研究C语言

没有读过第一篇的读者,可以点击这里,阅读深入研究C语言的第一篇。

一. 研究过程

问题一:如何打印变量的地址?

1.第一章:创建编译环境:

我们用取地址符&,可以取到变量的偏移地址,用DS可以取到变量的段地址。

我们首先下载TC2.0,找到其中与编译连接相关的程序和文件:

1.全局变量:

编译器:TCC.exe

永利皇宫 1

连接器:tllike.exe

永利皇宫 2

相关文件:c0s.obj、cs.lib、emu.lib、maths.lib

我们看到,这里的全局变量是在数据段中的。

将文件放在C:\C目录下。

2.局部变量:

编写程序测试我们的编译环境:

永利皇宫 3

永利皇宫 4

永利皇宫 5

在这里我们看到,程序被正常的编译。生成了.exe文件。并且可以正确执行。

我们看到,这里的局部变量是在栈段中的。

当然,在TC中,c0s.obj、cs.lib、emu.lib、maths.lib这四个文件时在TC目录下的lib文件夹下,但是我们如果将lib文件夹直接放入C:\C目录下,程序在编译的时候会提示:

问题二:研究main函数的偏移地址与源代码中main函数的定义位置之间的关系。

永利皇宫 6

我们打印函数的偏移地址,在打印的过程中我们可以发现:

C0s.obj:Unable to open file

当程序编码如下时,程序运行的结果是:

这是因为,TCC没有找到lib目录下的c0s.obj文件,我们可以推知,TCC默认在寻找文件的时候只在自己同层的目录下寻找。

永利皇宫 7

在这里我们发现,我们只用了TCC,并没有用到TLINK。那TLINK的作用是什么呢?

永利皇宫 8

我们删除TLINK。然后进行编译连接的工作。我们看到:

而将程序的f1函数和f3函数互换,程序运行的结果如下:

永利皇宫 9

永利皇宫 10

我们看到,TLINK其实是被TCC调用实现功能的。

永利皇宫 11

书中的解释是:TCC.EXE 将a.c编译成a.obj

可以看到,f1和f3的位置发生了改变,并且改变是相互颠倒了。

TCC调用TLINK将c0s.obj、cs.lib、emu.lib、maths.lib中的相关代码连接到一起生成.exe文件。

我们还知道C语言中有这样的函数声明定义方式:

在刚才的步骤中,虽然我们没有生成.exe,但是我们发现,生成了1.obj。那么,我们把TLINK重新找回来,能不能将这个1.obj连接成.exe呢?

永利皇宫 12

我们尝试:

我们查看他的结果:

永利皇宫 13

永利皇宫 14

我们发现我们成功的连接完成。

我们看到,在第一种方式下,f1—main的偏移地址依次增大;第二种方式中,f1与f3的偏移位置发生了互换。而在第三种方式中f1—main的偏移地址依然是依次增大的。从中我们可以得出结论:C语言程序的函数从01fa处开始。按照函数实现的顺序依次排列。在这里,函数从01fa处开始的原因是由于编译和连接的过程中,在我们编写的函数前添加了一部分固定长度的内容。

当我们用TCC编译时,程序可有两个最大为64K的段,一个段为代码段,栈和数据段共用一个段。我们如何来验证这一点呢?(注:这里其实是用到了后面的内容)。我们编写这样一个程序,让其显示程序运行时CS和SS,DS的值。

问题三:

永利皇宫 15

阅读TC2.0完整目录下的“HELPME!.DOC”,解决以下问题:

我们编译运行,查看结果:

a) TCC.exe与TC.exe的区别?

永利皇宫 16

b) TCC.exe和TC.exe生成的exe文件有什么不同?

我们发现,CS是一个值,DS,SS两者值相等。这样也就验证了代码段为一个段,栈和数据段共用一个段。而我们又知道,每个段地址不变,偏移地址从0000-ffff是64K的字节。所以这两个段的最大值是64K。

首先我们参见文档中说明:

另外,我们在CMD中直接输入TCC,会显示出TCC的使用参数,如下:

TCC.exe与Tc.exe的区别:TC.exe是一个集成环境,质上是命令行编译器集成编辑器,链接器和调试器。而TCC.exe只是一个命令行编译器。

永利皇宫 17

TCC.exe和TC.exe生成的exe文件的不同:问:为什么。TC生成的EXE文件比由TCC.EXE生成的文件要大.在默认配置下TC.EXE生成的exe包含调试的信息。而TCC.EXE生成的没有。

2.第二章:显示函数的段地址和偏移地址:

问题四:

我们继续研究第二章的内容:

进一步通过debug观察两个程序分别通过TC.exe与TCC.exe生成的exe文件,理解TC.exe与TCC.exe对代码不同的优化。两个程序为:

在main函数中添加语句,使下面的程序可以打印出所有函数的段地址和偏移地址。

a) 仅打印“Hello World!”的程序;

程序如下:

b) 拥有带参数的子函数的程序。

永利皇宫 18

首先我们看打印“Hello World!”的程序:

我们最直接的想法是用取地址的方式来查看。我们知道,在C语言中,&的作用是取地址。比如:

源码:

永利皇宫 19

永利皇宫 20

运行后结果如下:

看他们编译后的文件大小:

永利皇宫 21

永利皇宫 22

那么,函数是不是也可以这样来取地址呢?我们尝试:

既然是比较不同点,我们就反汇编试试:开始的反汇编代码都相同,我们直接-U到01fa。发现:

永利皇宫 23

永利皇宫 24

在这里,我们直接加类似&f1这样的取地址加函数名的形式可以么?我们分析:在debug中,我们看到子程序调用是都是执行的CALL的方式。在这里,函数名和标号有着类似的作用,就是方便编程人员编程、方便编译器编译和链接。他的本质应该是一个地址值。

左图为TC编译后,右图为TCC编译后

我们直接编译看看是否会报错,证实我们的猜想。

这里出现了明显的不同:

永利皇宫 25

我们往前查看一些:

我们发现没有报错。也就说明这里的函数名确实被翻译标号或与标号类似的东西。

永利皇宫 26

为了方便查看,我们让这些地址以16进制的方式显示出来。结果如下:

左图为TC编译后,右图为TCC编译后

永利皇宫 27

我们可以看出TC编译后的程序,在寄存器保护上比TCC编译后的更加全面。

那么我们所找到的值是不是函数的入口地址呢?我们进入debug查看:

我们再看有带参数函数的程序:

永利皇宫 28

网站地图xml地图