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

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

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

1.全局变量:

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

2.局部变量:

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

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

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

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

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

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

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

我们查看他的结果:

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

问题三:

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

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

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

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

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

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

问题四:

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

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

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

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

源码:

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

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

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

这里出现了明显的不同:

我们往前查看一些:

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

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

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

分别编译并debug反汇编查看:

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

我们看到,左图比右图多更多的寄存器保护的语句。

对于代码优化,TC更多的舍弃了效率和文件大小,来保证程序的安全性。而TCC更多的舍弃了程序的安全行,来生成精简的C程序,使得程序更加简短和高效。

问题五:第2章中,程序需要打印函数的段地址和偏移地址,在command中直接运行和在debug中运行打印的段地址不同,偏移地址相同,这是什么原因?

在这里,debug是用来调试程序的,他可以控制程序单步执行,并且查看程序运行中各种寄存器的状态。要做到这一点,debug肯定有他自己的控制方式。他需要将程序从debug内加载。而cmd运行程序,是系统执行的方式。他只需要接受系统的调用就可以执行。由于他们的运行方式不同,所以他们的段地址不同。但是,程序编译完成后,他的程序内的偏移地址就确定了(就像能我们打印main函数的偏移地址,说明这个地址是确定的)。而且每个程序都最大有64K的程序段和64K的数据段和栈段的混合段。所以在系统每次分配的时候,给每个程序都分配固定大小的但是位置不同的内存(一个64K的程序段,一个64K的数据段和栈段的混合段),即栈地址固定,偏移地址从0000-FFFF的内存。

问题六:第2章中,同时用多个dos窗口加载程序,打印所得的段地址和偏移地址都相同,这是什么原因?

在《汇编语言》书中,附注1的内容介绍了Inter系列微处理器的3种工作模式。

(1) 实模式:工作相当于一个8086。

(2) 保护模式:提供支持多任务环境的工作方式,建立保护机制。

(3) 虚拟8086模式:可以从保护模式切换至其中一种8086工作方式。这种方式提供使用户可以方便的在保护模式下运行一个或多个原8086程序。

而我们的windows是基于80386的。我们可以这样轻松的工作,开两个窗口,一个是工作于保护模式的word,一个是工作于虚拟8086模式的DBASE。

也就是说我们现在的command是虚拟8086的模式下工作的。既然是虚拟的,两个command之间就没有什么关联。两个command之间也就不会共用一段真实的内存(他们的内存是虚拟出来的)。所以打印所得的段地址和偏移地址都相同。

问题七:我们使用基于tc2.0的精简开发环境进行综合研究是为什么,这样做对我们有什么帮助?

使用精简的开发环境,可以减少我们的所面对的问题,集中精力解决我当前所要研究和解决的,C语言的基本问题。并且,这样做,我们可以更加深入的研究C程序在编译连接中所用到的深层次的、必须需要的过程。

问题八:语句printf(”%x %x %x\n”, main, &main, *main);打印的结果都是同一个值,试着解释原因。

我们编写这样一个程序:

这可以说明,printf是可以显示常量的。并且&和*常量,显示的都是常量的值。

我们知道,在汇编编译连接的过程中,其实是进行了两次,第一次是编译各种机器码,这是并不知道标号是在什么位置,第二次是在第一次完成后再次将翻译编译标号的地址。

C语言也有可能这样,main被翻译成了main函数所在的偏移地址(一个常量,这个常量的值是由第一次编译确定的)。这样,也就可以说明(long)main出来后显示的是段地址偏移地址的问题。

问题九:使用更多的方法完成打印main函数偏移地址

我们可以使用这样的方法打印:

我们还有这样的办法打印:

问题十: tcc精简环境编译生成的exe文件中的程序可有两个最大为64k的段,那么当我们需要的代码段或者数据段超出64k怎么办?

我们拿数据段做验证,首先计算数据段的大小:

我们知道,一个int型变量在内存中占两个字节,而数据段和栈段共用一个段。段的大小是64K,我们计算64K的数据段能存放多少个int型数据。答案是最多存放32767个int型。我们编写程序如下:

我们故意将数组的数量设为32768,结果发现在编译的时候TCC报错:说我们定义的数据超出范围。这说明在编译的时候,程序会自动检查你编写的程序的数据长度,如果超出,则编译不通过。(注,此时没有TLINK.exe文件)

我们将数组的值设为32767,然后编译,其错误信息如下:

这说明TCC的编译工作已经正常完成,但是由于没有TLINK.exe文件,无法生成.exe。我们放入TLINK.exe,再次编译。

我们发现其仍然提示段已经超出64K。直到数值粗略改到32500左右时,不再报错。

这是为什么呢?这里提出两种可能:

1. 编译连接器自动给程序保留栈:因为程序在执行的过程中不可避免的要用到栈,所以在连接的时候,编译连接器自动给程序保留了一部分栈内存。当它发现这部分内存加上本身定义的数据超过了64K,就报错了。

2. 编译连接器在编译连接过程中向数据段写入了内容:程序连接的过程,是TLINK.exe将c0s.obj、cs.lib、emu.lib、maths.lib中的相关代码与程序的代码连接到一起生成.exe文件。在这个过程中有可能向程序段中加入了数据。导致程序段超出64K。

对于两种猜想我现在还没有想到非常巧妙的方法证明,只能暂时这样猜想。

而:当我们的程序不得不大于64K时。我们可以向系统申请内存,或者使用没人使用的安全内存,将数据或者程序写入这段内存中,在从这段内存中使用数据或者调用代码。

深入研究C语言 第一篇(续)的更多相关文章

  1. 深入研究C语言 第一篇

    一. 研究过程 1.第一章:创建编译环境: 我们首先下载TC2.0,找到其中与编译连接相关的程序和文件: (1) 编译器:TCC.exe (2) 连接器:tllike.exe (3) 相关文件:c0s ...

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

    1. 关于如下的程序,关于结构体的拷贝,拷贝是拷贝到内存中的什么地方? 我们进入debug进行反汇编,单步等操作跟踪查看.发现: 在main中,我们看到call 0266应该对应的是转跳到func处执 ...

  3. 深入研究C语言 第二篇

    1. 程序一: 首先我们研究如下程序: 回答如下问题: 1. 程序运行时n,a,b,c的段地址在哪个寄存器中? 全局变量的存储空间在什么段里?局部变量的存储空间在什么段了?参数在什么段里?函数的返回值 ...

  4. C语言第一篇博客

    你对网络专业或者计算机专业了解是怎样? 进行网络安全,防止信息泄露. 你了解C语言么?C语言主要应用有哪些? C语言简言之就是一门计算机的编程语言. C语言主要应用于应用软件,服务器端开发,系统软件和 ...

  5. Scala语言笔记 - 第一篇

    目录 Scala语言笔记 - 第一篇 1 基本类型和循环的使用 2 String相关 3 模式匹配相关 4 class相关 5 函数调用相关 Scala语言笔记 - 第一篇 ​ 最近研究了下scala ...

  6. 如何起草你的第一篇科研论文——应该做&避免做

    如何起草你的第一篇科研论文——应该做&避免做 导语:1.本文是由Angel Borja博士所写.本文的原文链接在这里.感谢励德爱思唯尔科技的转载,和刘成林老师的转发.2.由于我第二次翻译,囿于 ...

  7. [老老实实学WCF] 第一篇 Hello WCF

    老老实实学WCF  第一篇 Hello WCF WCF(Windows Communication Foundation)是微软公司推出的面向服务技术的集大成者,涵盖继承了其之前发布的所有的分布式应用 ...

  8. 老老实实学WCF[第一篇] Hell wcf

    老老实实学WCF  第一篇 Hello WCF WCF(Windows Communication Foundation)是微软公司推出的面向服务技术的集大成者,涵盖继承了其之前发布的所有的分布式应用 ...

  9. (转)[老老实实学WCF] 第一篇 Hello WCF

    http://blog.csdn.net/songyefei/article/details/7363296#comments 老老实实学WCF  第一篇 Hello WCF WCF(Windows ...

随机推荐

  1. 关于nginx的1W并发的优化

    我们来看一下图,下面的这张图清晰的表明了nginx优化的一些方法: nginx要响应请求的话,必须要: 1.要建立socket连接 2.是要读本地的文件 所以这就是我们的一个优化的方向: 所以参考照上 ...

  2. Windows2008 R2 Enterprise离线安装IE10和VS2015过程记录

    直接下载IE10,进行安装,提示需要联机下载更新: 在网上搜索到一篇文章(http://www.cnblogs.com/nbpowerboy/p/3383992.html),参考 以下载简体中文的Wi ...

  3. RocketMQ与Kafka对比(18项差异)

          转自:https://github.com/alibaba/RocketMQ/wiki/rmq_vs_kafka 淘宝内部的交易系统使用了淘宝自主研发的Notify消息中间件,使用Mysq ...

  4. DataSnap修改数据ApplyUpdates出现错误:连接繁忙导致另一个命令

    最近准备尝试用DBExpress做个SQL Serer应用,在学习的时候发现一个问题使用DBExpress连接Sql server 2008 express使用以下控件SQLConnection-&g ...

  5. android 音频焦点

    音频焦点分为两种 1永久占用((AudioManager) getSystemService(AUDIO_SERVICE)) .requestAudioFocus(null, AudioManager ...

  6. c/c++创建动态链接库

    extern "C" C++保留了一部分过程式语言的特点,因而它可以定义不属于任何类的全局变量和函数.但是,C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局 ...

  7. MySQL中auto_increment的基本特性

    创建数据表时,经常会出现auto_increment这个词,下面就来了解一下它吧. MySQL的中AUTO_INCREMENT类型的属性用于为一个表中记录自动生成ID功能,可在一定程度上代替Oracl ...

  8. 网络编程——URL编程

    URL:是统一资源定位器的简称,它表示Internet某一资源的地址.通过URL我们可以访问Internet上的各种网络资源,比如最常见的www,ftp站点.浏览器通过解析给定的URL可以在网络上查找 ...

  9. python打印服务器所有进程

    #有时候我们需要查看服务器上所有进程,来判断哪些进程是否已经称为僵尸进程#!/usr/local/bin/python3.5 import psutil for i in psutil.pids(): ...

  10. Android部分调试开关

    开启 GPU Render 的profiling bar: adb shell setprop debug.hwui.profile visual_bars #或visual_lines adb sh ...