深入研究C语言 第一篇

一. 研究过程

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

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

(1) 编译器:TCC.exe

(2) 连接器:tllike.exe

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

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

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

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

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

C0s.obj:Unable to open file

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

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

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

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

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

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

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

我们尝试:

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

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

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

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

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

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

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

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

程序如下:

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

运行后结果如下:

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

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

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

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

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

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

我们看到,在01fa中,是我们定义的F1函数中的语句,int a=1;也就是说我们的想法是正确的,在printf中直接取函数的地址输出是可以的。

但是,我们看到,我们打印出的是函数的偏移地址,段地址如何打印呢?

我们知道,在C语言中,我们可以直接调用一些汇编的寄存器,而在汇编中,CS寄存器记录的是程序段的段地址,也就是说,我们只要显示出CS,就可以显示出程序段的段地址。

我们编写:

运行后其结果如下:

但是这个结果对不对呢?我们还得从debug中验证:

我们看到,在Debug中-g运行后的结果与CS的值相同。都是0b3b。这也就说明了程序所显示就是程序在执行时的程序段地址。

那么这两次执行2.exe所显示的程序段地址为什么会不同呢?我们知道,第一次我们是直接在cmd中运行的2.exe,程序接受系统调用自己执行。而第二次我们是用debug加载进入系统,然后执行。在debug加载的时候,程序被debug加载到了指定的程序段位置。

二. 附加研究:

在进行研究时,我发现在C语言显示变量的偏移地址时,不同的变量显示的地址值是不相同的。

比如,全局变量显示如下:

局部变量显示如下:

这个结果让我疑惑,但是我想起了局部变量和全局变量的区别,又看到了-32这样的值,我猜想这应该是局部变量记载的是相对位置而不是绝对位置。因为绝对位置不会出现负数。我编写程序如下:

在debug中,我-g到第二个printf();函数前,也就是显示出变量a的地址后。

通过当前的SS:SP的值与-28进行计算,查看结果单元,发现:

这个单元内所存放的数就是变量a的值,也就是说这个单元就是变量a的存储单元。

所以得出结论:在C语言中,全局变量的地址是记录其存储单元的偏移地址,而局部变量的地址是记录其存储单元与现在栈顶指针的相对位置。

(注解,重要:在这里后期我在回头看的时候,发现这里使用的是%d方式,这很重要。因为地址是FFE4,这是一个十六进制的数,本应该用%f的方式显示出来。而正是两种数不同的表达和显示方式造成了这样的现象。而非是上面的结论。这里为了过程的完整而保留了这个问题。在此改正。)

时间: 2024-08-02 23:13:36

深入研究C语言 第一篇的相关文章

深入研究C语言 第一篇(续)

没有读过第一篇的读者,可以点击这里,阅读深入研究C语言的第一篇. 问题一:如何打印变量的地址? 我们用取地址符&,可以取到变量的偏移地址,用DS可以取到变量的段地址. 1.全局变量: 我们看到,这里的全局变量是在数据段中的. 2.局部变量: 我们看到,这里的局部变量是在栈段中的. 问题二:研究main函数的偏移地址与源代码中main函数的定义位置之间的关系. 我们打印函数的偏移地址,在打印的过程中我们可以发现: 当程序编码如下时,程序运行的结果是: 而将程序的f1函数和f3函数互换,程序运行的结

深入研究C语言 第二篇

1. 程序一: 首先我们研究如下程序: 回答如下问题: 1. 程序运行时n,a,b,c的段地址在哪个寄存器中? 全局变量的存储空间在什么段里?局部变量的存储空间在什么段了?参数在什么段里?函数的返回值存储在什么地方? 全局变量的存储空间在什么时候分配?什么时候释放? 局部变量的存储空间在什么时候分配?什么时候释放? 2. 函数f3在调用与返回方式与函数f1与f2有何不同? 我们编译完成后,进入debug查看. 首先,我们执行到main函数处,然后开始单步执行.我们看到,每次单步执行时,涉及到取数

深入研究C语言 第二篇(续)

1. 关于如下的程序,关于结构体的拷贝,拷贝是拷贝到内存中的什么地方? 我们进入debug进行反汇编,单步等操作跟踪查看.发现: 在main中,我们看到call 0266应该对应的是转跳到func处执行. 在这里,func赋值完成后,又call到了0B3D:13EA处,这里应该是其向内存中复制的函数.我们查看. 首先我们看LDS:从存储器取出32位地址的指令.和LES:LES( load ES)指令的功能是:把内存中指定位置的双字操作数的低位字装入指令中指定的寄存器.高位字装入ES寄存器. 我们

go语言第一篇。九大优势和3个缺点。

go语言优势: Go 极其地快.其性能与 Java 或 C++相似.在我们的使用中,Go 一般比 Python 要快 30 倍.以下是 Go 与 Java 之间的基准比较: 原因 2:语言性能很重要 对很多应用来说,编程语言只是简单充当了其与数据集之间的胶水.语言本身的性能常常无关轻重. 但是 Stream 是一个 API 提供商,服务于世界 500 强以及超过 2 亿的终端用户.数年来我们已经优化了 Cassandra.PostgreSQL.Redis 等等,然而最终抵达了所使用语言的极限.

Go语言第一篇

第一次写博客,真的有点不知道如何去组织语言,慢慢来吧!哈哈! 首先,GO语言安装环境.安装了好多次有时候还是有点忘,现在对于Mac OS系统如何配置beego环境还是有点不明白.其实还是需要明白原理. 现在我在看<GO语言编程入门与实战技巧>这本书,再这里就不说GO语言的发展史以及GO的优缺点了.接下来,写一下知识点吧! 一.环境变量 1.GOROOT:表示GO语言环境在计算机的安装位置,这个变量只有一个值,必须是绝对路径. 2.GOPATH:这是GO语言的工作目录,可以又对多个,类似于工作空

第一篇---------c语言的一些基础零碎知识所思所想

首先,我的博客地址是http://www.cnblogs.com/naiwenmoer/,这也是我第一篇博客,以前没想过写博客,现在有幸加入这个大神遍地飞的园子,还请各位大神多多指教了! c语言作为经典语言,这里不再多说了.咱从基础一起探讨吧! 一. 定义一个整型,如果作为局部变量,没有初始化的情况下,它是一个随机的值的,一般情况下输出会是0,但这个0是作为垃圾值的;而如果作为全局变量,没有初始化的情况下,它的值是0. 二.我们都知道,static是用于声明一个内部全局变量,在使用static的

C语言中容易被忽略的细节(第一篇)

前言:本文的目的是记录C语言中那些容易被忽略的细节.我打算每天抽出一点时间看书整理,坚持下去,今天是第一篇,也许下个月的今天是第二篇,明年的今天又是第几篇呢?--我坚信,好记性不如烂笔头. 1. 在C语言中,符号之间的空白(包括空格符.制表符或换行符)将被忽略.但一个符号的中间不能有空白,否则可能被解释为另一个或几个符号.以下两种写法是等价的: //写法1 if (x > big) big = x; //写法2 if ( x > big ) big = x ; 2.编译器将程序分解为符号的&q

从Hello, world开始认识IL &lt;第一篇&gt;

IL代码分析方法 Hello, world历史 .NET学习方法论 1.引言 1988年Brian W.Kernighan和Dennis M.Ritchie合著了软件史上的经典巨著<The C programming Language>,我推荐所有的程序人都有机会重温这本历史上的经典之作.从那时起,Hello, world示例就作为了几乎所有实践型程序设计书籍的开篇代码,一直延续至今,除了表达对巨人与历史的尊重,本文也以Hello, world示例作为我们扣开IL语言的起点,开始我们循序渐进的

Azure IoT 技术研究系列1-入门篇

物联网技术已经火了很多年了,业界各大厂商都有各自成熟的解决方案.我们公司主要搞新能源汽车充电,充电桩就是我们物联网技术的最大应用,车联网.物联网. 互联网三网合一.作为Azure重要的Partner和使用者,我们对Azure的IoT方案也是非常期待的,因此,最近计划研究一下Azure的IoT技术,同时将研究的成果分享给大家. 以本文作为IoT入门的第一篇吧. IoT:Internet of Things,即连接一切. Azure提供了Azure IoT Hub:直译为Azure的物联网中心. A