A!die Software Studio Welcome to A!Die Software Studio

TURBOC六种编译模式概述

by other
2011-05-29 09:49:58
六种编译模式概述
Turbo C 提供了六种编译模式。编译模式有时也称为寻址模式或
内存模式,因为它处理的就是如何在内存中为程序,数据,堆栈分配
空间并存取它们,这六种模式是:微模式tiny,小模式small, 紧凑
模式compact,中模式medium,大模式large,巨模式huge。它们之间
的关系如下表所示。 │ 小程序 │ 大程序
━━━━┿━━━━━━┿━━━━━━━━
小数据 │ 微,小 │ 中
大数据 │ 紧凑 │ 大,巨 所谓小程序就是只有一个程序段,当然不超过64K 字节,缺省的
码(函数)指针是near。所谓大程序就是有多个程序段,每个程序段不
超过64K字节,但总程序量可超过64K字节,缺省的码指针是 far。所
谓小数据就是只有一个数据段,缺省的数据指针是near。所谓大数据
就是有多个数据段,缺省的数据指针是 far。下面还会逐个谈到它们
之间的差别,并通过同一程序在六种不同模式下的输出结果,来进一
步加深对这六种模式的理解。但先要强调一点:无论使用哪一种编译
模式,单个的Turbo C源文件不可能生成大于64K字节的代码,也不能
生成大于64K字节的静态(包括全局)数据。例如下面这个程序: int a[15000],b[20000];
void main(){}
在任何模式下都不能编译。这是因为,两个数组所要求的总存储量达
70K字节。编译时会报出"Too much global data defined in file"
的出错信息。为了处理大于64K 字节的代码或静态数据,必须分成几
个源文件。以上面这个程序为例,可以分成文件A1.C和A2.C,分别用
巨模式对这两个源文件进行编译,最后连接成一个可执行文件。 al.c a2.c a.prj
int a[15000]; int b[20000]; a1
void main() a2
a1.obj(30k) a2.obj(40k) a.exe(71k)
六种编译模式的差别是:它们对来自不同源文件的码和数据段的
处理不同,对动态分配的堆空间的处理不一样,对指针使用也不一样。
此外,它们的所形成的 .obj 文件中传给连接程序的信息也不一样,
以便连接程序相应地安排码段和数据段,把相应的说明放在 .exe 文
件的头中并借此通知DOS:当执行这个程序时如何装入码段和数据段,
如何设置各个段寄存器。
用于演示六种编译模的程序是由两个源文件X.C和Y.C组成,如下
所示:
/* X.C */
#include<general.h>
void a()
{
static int b;
int c;
printf("In function A \n");
printf(" CS : %X \n",_CS);
printf(" DS : %X \n",_DS);
printf(" SS : %X \n",_SS);
printf(" Static B : %p \n",&b);
pritnf(" Automatic C : %p \n",&c);
}
/* Y.C */
#include<general.h>
int d;
void main(){
int e;
a();
printf("In function main \n");
printf(" CS : %X \n",_CS);
pritnf(" DS : %X \n",_DS);
pritnf(" SS : %X \n",_SS);
pritnf(" Global D : %p \n",&d);
pritnf(" Automatic E : %p \n",&e);
printf(" Heap address : %p \n",malloc(2));
#if defined(__TINY__)||defined(__SMALL__)||defined(__COMPACT__)
pritnf("Function A : %Np \n",a);
pritnf("Function main : %Np \n",main);
}
#else
printf("Function A : %Fp \n",a);
printf("Function main : %Fp \n",main);
}
#endif 第一个源文件包含函数a和一个静态(局部)变量b,第二个源文件
包含主函数main和一个全局变量d 。两个源文件中各含有一个自动变
量c和e。第二个源文件的主函数main调用了第一个源文件中的函数a,
还调用了Turbo C 的库函数malloc去分配一块堆空间。两个源文件是
分别编译的,然后再通过连接程序连接起来。
通过以六种不同模式编译这两个源文件,可以看到它们是如何为
码、数据和堆栈段分配空间,可以看到静态变量、自动变量和堆变量
分别存放在什么位置,函数放在什么地方。正如下面将要看到的那样,
在某些模式下,数据指针是near而函数指针是 far;在另一些模式下 
情况又正好相反。
对于数据指针,不管是far还是near,pritnf函数中的格式说明 %p都
能把指针正确地打印出来。对于函数,指针%p就没有这个功能。所以,
在main函数中必须加条件编译控制行#if、#else和#endif。微模式
在微模式下,整修程序只有一段,这个段内包含码、静态和全局
数据、堆栈和堆。因为只有一个段,在执行时DOS将把寄存器CS、DS、
SS设置为相等,全都指向这个段。在这个段内,码是首先装入的,地
址最低,接着是静态变和全局变量,然后是堆,最后地堆栈。堆和堆
栈都是动态的,堆从低地址往高地址增长,堆栈从高地址往低地址增
长。若两者相碰,则表示内存空间已耗尽。在微模式下,所有指针都
是near,且都是相对于寄存器CS、DS和SS的。对于用微模式编译并连
接生成的 .exe文件,DOS的exe2bin实用程序转换为 .COM文件。
从下表演示程序的输出结果可以看出,函数a 比函数main的地址
低,变量b比变量d的地址低。这是因为,在连接时是x.obj在前,
y.obj在后。
微模式 小模式 紧凑模式 中模式 大模式 巨模式
In function A
CS : 74C8 74B1 74B1 74F9 74FD 74FE
DS : 74C8 75CC 7629 75EC 764A 7674
SS : 74C8 75CC 767A 75EC 76A0 76BB
Static B : 1704 048C 7629:04C8 049A 764A:04D6 7674:0002
Automatic C : FFD0 FFD0 767A:0FD6 FFCC 76A0:0FD4 76BB:0FD0
In function main
CS : 74C8 74B1 74B1 74FE 7502 7503
DS : 74C8 75CC 7629 75EC 764A 767B
SS : 74C8 75CC 767A 75EC 76A0 76BB
Global D : 1706 048E 7629:04CA 049C 764A:04D8 767B:0004
Automatic E : FFD6 FFD6 767A:0FDE FFD4 76A0:0FDC 76BB:0FDA
Heap Address: 1792 051A 777C:000C 0568 77A2:000C 77BD:000C
Function A : 0283 01A5 0167 74F9:000E 74FD:000D 74FE:0003
Function main: 02C1 01E3 01AE 74FE:0004 7502:000C 7503:0009小模式
小模式是常用的模式,本书中大部分例子都是用小模式编译的。
虽然小模式与微模式一样,都是小数据、小程序模式,但它与微模式
有两点重要的差别。第一,码和数据/堆栈/堆段是分开的,所以CS不
等于DS和 SS。第二,除了和数据/堆栈共用一个段的堆外,还有一个
远堆,以far指针进行存取。从数据/堆栈段的末尾直到常规内存的末
尾都是属于远堆。
因为码、静态数据和(近)堆仍然在同一个段内,所以小模式下缺
省的数据指针和函数指针都是near。结果,在小模式下不能直接通过
该模式下的Turbo C 函数来处理远堆中的变量。然而,只要程序提供
自己的操作函数,就可以存取整个远堆中的任一单元,即可以使用整
个常规内存。中模式
在数据/堆栈/堆的分配方面,中模式与小模式是一样的,差别在
于码段的分配。在中模式下,来自不同源文件的码模式放在不同的码
段内。严格地讲,同一源文件内的各函数也是放在不同码段内。各码 
段的总空间数只受微机上可用内存的限制。因为有多个码段,所以
Turbo C必须用far函数指针。在演示程序输出的结果中函数a 的地址
为74F9:000E,函数main的地址为74FE:0004。函数a 的地址较低,是
因为在连接时包含函数a的x.obj在前。在中模式下,堆仍然有近堆和
远堆之分。紧凑模式
紧凑模式在概念上是最简单的,码、静态数据、堆栈、堆各有其
自己的段。堆只有远堆,没有近堆。像小模式和中模式中的远堆一样,
堆是用far指针来存取的。可以用Turbo C的库函数来处理堆变量。所
有数据指针都是far,函数指针都是near。 从演示程序的输出中可以 
看出,CS、DS、SS三个寄存器的值彼此不等。值得注意的是,静态数
据的总量仍不可超过64K字节。大模式
在静态数据/堆栈/堆的分配方面,大模式等同于紧凑模式。在码
的分配方面,大模式等同于中模式。无论是数据指针还是函数指针,
一律都是远指针。与紧凑模式一样,静态数据的总量不可超过64K 字
节。巨模式
巨模式取消了静态数据总量不可超过64K 字节的限制。来自不同
源文件的码放在不同的段内,来自不同源文件的静态数据也是放在不
同的段内,只有堆栈是合在一起的。以前举的例子就是利用了这一特
点。从演示程序的输出中也可以看,当从函数main内调用函数a 时,
不但CS改变了,DS也改变了。当然,两个函数共用了同一个堆栈,否
则就无法正确返回。应该注意的是,不要把巨模式和巨指针混为一谈,
在巨模式下缺省的指针仍是far而不是huge。堆栈的组织
Turbo C 堆栈是用来存储其生命期与函数生命期相同的数据,这
样的数据包括函数参数和函数体内定义的自动变量。为了表明函数堆
栈内部各数据的存放关系,设有这样一个函数定义: long f(char a,int b)
{
int c;
char d;
....
每当调用函数f 时,调用进行首先按相反顺序,即按从右到左的
顺序把调用参数压入堆栈。本例就是先压入 b,再压入a。尽管参数a
是字符型,但仍压入16位,因为80X86的机器没有8位的压栈指令。在
压入参数之后,根据调用指令是near还是far,再压入 2个或4个字节
的返回地址。
进入被调用函数 f之后,它首先把寄存器BP的当前值压入堆栈,
并把SP寄存器的值拷贝到BP寄存器。接着再次执照相反的顺序在堆栈
内建立起函数体内的各个自动变量,本例里就是先d后c。直到此时,
堆栈的内容将会如下所示:
....
b
a
返回地址
保留的BP
d
c 这里之所以要对BP和SP 作如此处理,目的有3个。第一,为了利
用BP作地址寄存器,通过[BP±n] 这样的寻址方式到堆栈中存取调用
者传过来的参数和被调用函数自己的自动变量。因为在80X86中规定,
当用BP作地址寄存器时,缺省的段地址是SS而不是DS。第二,腾出DS
和其它地址寄存器,仍用来存取缺省的数据段内的数据。第三,腾出
SP以便在函数体内再调用其它函数。
当函数 f完成了它的工作以后,它就把返回值放到相应的位置。
如果返回值是char型,则在返回前先强制转换为 int型。凡是返回值
占两个字节的都通过寄存器AX返回,凡是返回值占 4个字节的都是通
过寄存器对DX:AX返回。超过4个字节的struct返回值,则被放在一个
静态变量内,返回的是指向这个变量的指针。dboule返回值是放在数
值协处理器的top_of_stack寄存器或协处理器软件模拟包内与这个寄
存器等效的地方。接着函数f 把BP拷贝到SP,从堆栈中弹出入口时保
留的BP值到BP寄存器。最后执行一条near 或far返回指令,返回到调
用者。返回以后,调用函数必须把调用调用时压入堆栈的参数从堆栈
中清除。
上面这一套函数调用规则就叫做 C调用规则。从这个过程中可以
看出,调用函数和被调用函数在参数数目上可以不一致。如果调用函
数压了过多的参数,被调用函数不存取这些多余参数是没有什么影响 
的,调用函数在重新获得控制权后,会正确地把这些参数清除掉。如
果调用函数压入了过少的参数,被调用函数就可能把一些并非参数的
内容取来人微言轻参数而产生意想不到的结果。为了克服这个困难,
如果参数数目是不定的,那么第一个参数最好是说明随后的参数的个
数。
另一套不同的函数调用规则叫做PASCAL规则,它与 C调用规则有
两点重要的差别。第一,压入参数的顺序是从左到右。第二,被调用
函数的工作完成以后,从堆栈中弹出参数是由被调用函数而不是由调
用函数去完成。PASCAL调用规则要求调用函数和被调用函数参数上数 
完全一致。顺便说一句,Turbo PASCAL语言使用的不是PASCAL调用规
则,而是一种更为精心设计的堆栈格式,使得从被嵌套的函数内可以
存取函数的自动变量。堆的组织
前面已经说过,在小模式和中模式下,堆有近堆和远堆之分,处
理办法也不一样。近堆和堆栈共享一个段,它们相向增长,如果相遇,
则说明缺省数据段已耗尽。远堆使用了缺省数据段之上直至常规内存
末尾的整个空间。为了管理这两个堆,Turbo C 提供了两组相应的函
数:
coreleft farcoreleft
realloc farrealloc
malloc farmalloc
free farfree
calloc farcalloc 左边的近堆函数用近指针寻址各个堆变量,所用的参数也都16位
的unsigned型。右边的远堆函数用远指针寻址各个堆变量,所用的参
数也都是unsigned long型。
在微模式下没有远堆,在紧凑模式、大模式和巨模式下只有一个
不改堆,其组织形式如同远堆。但在这三种模式下,既可以使用近堆
函数也可以使用远堆函数存取堆中变量。这是因为,不管使用哪一种
堆函数,这三种模式决定了所有数据指针是far。如果使用近堆函数,
则表示所需容量的参数size还必须是unsigned型的16位数。如果必须
处理大于64K字节的内存块,还必须使用远堆函数。
分配和释放是随机进行的,没有一定的次序,结果就造成了各个
堆变量在堆中是不连续的。Turbo C 用一个链表来处理这些堆变量。
在每一个堆变一的前面都有一个头,头中包含两个信息:此变量的长
度和指向下一个堆变量的指针。对于小数据模式,每个头占4个字节,
对于大数据模式,每个头占8个字节。
为了说明分配、释放、再分配在堆中是如何进行的,请看下面这
个演示程序htap.dem的输出结果。#include<stdio.h>#define report printf("coreleft=%u\n",coreleft());void main()
{
void *p,*q,*r;
printf(" ");report;p=malloc(1);
printf("p=malloc(1) =%p;",p);report;q=malloc(2);
printf("q=malloc(2) =%p;",q);report;q=realloc(p,3);
printf("p=realloc(P,3)=%p;",p);report;r=malloc(1);
printf("r=malloc(1) =%p;",r);report;free(q);
printf(" free(q) ");report;free(p);
printf(" free(p) ");report;
}
这个程序产生的输出如下:
coreleft = 63952
p=malloc(1) = 0500; coreleft = 63946
q=malloc(2) = 0506; coreleft = 63940
p=realloc(P,3) = 050c; coreleft = 63932
r=malloc(1) = 0500; coreleft = 63932
free(q) coreleft = 63932
free(p) coreleft = 63946 这个演示程序是用小模式编译的。首先,coreleft报告可用的内
存量。其次,malloc建立单字节堆变量p和双字节变量q。因为总是以
2字节整数倍进行分配的,所以单字节变量p实际上也占用两个字节的
空间。每个堆变量还需要 4个字节的头。这样,每分配一个堆变量,
内存容量就减少6个字节。接着,realloc把变量p扩大到3个字节,这 
就要求重新分配,返回的指针也指向了新地址050C。重新分配的堆变
量p占用了8个字节。包括它的头。尽管此时原来占用的 6具字节已经
释放了,但coreleft仍报告减少了 8个字节,而不是减少了两个字节。
这是因为coreleft报告的只是堆中最上面最后一个变量之后连续可用
的内存容量。也就是说由于堆的碎片化,coreleft报告的值是不准确
的。接着,程序又分配了一个单字节变量 r,它占用了第一次分配给
变量p后来又被释放的那6字节空间。在些之后,程序释放变量 q,在
变量r和p之间留下一个空洞。应该注意,分配r和释放q都不影响
coreleft 报告的值。最后,程序释放变量p。此时,coreleft报告的
值才是准确的,因为堆中只在其开始部分剩一个变量r了。
下面这个farheap。dem程序演示了如何从远堆中分配一个大于
64K字节的数组a。数组a是由9000具double型元素组成的,共需72K
字节。把函数farcalloc返回的远指针强制转换为huge指针, 以后就
可以用这个huge指针存取数组中的各个元素。#include<malloc.h>
void main(){
int i,n=9000;
double huge *a;
double sum;
a=(double huge *)farcalloc(n,sizeof(double));
for(i=0;i<n;a[i++]=i);
for(i=0,sum=0;i<n;sum+=a[i++]);
printf("a[i]=i for i=0。 n-1; n=%d\n",n);
printf("sum of all a[i]=%8.0f\n",sum);
printf("(n-1)n/2 =%1d \n",(long)n*(n-1)/2);
}
其它内存操作函数
Turbo C 中还提供了许多有关内存拷贝、比较、设置和查找的函
数。这些函数的说明都在头文件mem.h 中。一般来说,它们都不牵涉
到什么结构,而是直接对内存进行操作。这些函数可对简单的字节进
行操作,也可实现C 语言不直接支持的对数据结构的操作,如用一个
数组对另一个数组赋值,数组或C结构之间的比较等。
用于内存之间拷贝数据的Turbo C函数有如下5个:
void *_Cdecl memccpy(void *dest,const void *src,
int c,size_t n);
void *_Cdecl memcpy(void *dest,const void *src,
size_t n);
void *_Cdecl memmove(void *dest,const void *src,
size_t n);
void _Cdecl movmem(void *src,void *dest,unsigned length);
void _Cdecl movedata(unsigned srcseg,unsigned srcoff,
unsigned dstseg,unsigned dstoff,size_t n); 函数memcpy从源 src拷贝kn个字节到目dest。如果源和目有重叠
的地方,则结果不一定正确。
函数memccpy与memcpy类似,但若被拷贝的字节中有字符c,则在
拷贝完这个字符后也停止拷贝,返回的指针指向目dest中的下一个字
节位置。若n个字节全部拷贝完,则返回的指针为空。
函数memmove和movmem 也用于拷贝,但它们都解决了源和目重叠
的问题。函数movmem 一反通常“目=源”这样一个参数顺序,而是源
在前,目在后。
在小模式和中模式下,前面4 个拷贝函数所接收的源和目指针都
只能是近指针,不能用来拷贝远数据段内的数组。函数movedata克服
了这个缺陷,它允许指定源和目的段地址和偏移量,它没有解决源和
目重叠的问题,也要求源参数在前,目参数在后。
Turbo C Tools中的函数utmovmem与Turbo C的movedata是类似的,
但它自动解决了源和目的重叠问题。 void utmovmem(const char far *psource,char far *ptarget,
unsigned int length);
用于内存之间比较的Turbo C函数有如下两个: int _Cdecl memcmp(const void *s1,const void *s2,size_t n);
int _Cdecl memicmp(const void *s1,const void *s2,size_t n); 这两个函数都是比较两个字节数组的前n 个字节,根据s1是小于、
等于还是大于s2,返回值分别为小于0、等于0和大于0。但函数memcmp
是精确比较,把每个字节看作无符号8位数,而函数memicmp把每个字
节看作一个字符,忽略大小写的差别。
用于内存设置的Turbo C函数有如下两个: void *_Cdecl memset(void *s,int c,size_t n);
void _Cdecl setmem(void *dest,unsigned length,char value);
这两个函数都是设置一块内存区域为某一个字节值,参数顺序不
一样,返回值也不一样,但实际作用看不出有什么区别。
用于从一个内存块的头n个字节中查找某一个字符的Turbo C函数
是memchr: void *_Cdecl memchr(const void *s,int c,size_t n); 如果找到了,则返回的指针向字符c第1次出现的位置。如果找不
到,则返回的指针为空。

▲评论

› 网友 王树坡 () 于 2013-08-06 15:21:33 发表评论说:

受教了,谢谢您!
我是王树坡,刚从学校毕业,打算从事计算机行业,能得到您的指点吗~~~
1121140449
我的QQ

› 网友 Sarah Carlson (sex:oohrktyyrmi@ivasns.com; email:Sarah Carlson; web:http://v-doc.co/nm/jkfq0; qq:Sarah Carlson; from:Sarah Carlson; work:Sarah Carlson; ) 于 2017-08-20 01:54:57 发表评论说:

I came to your TURBOC六种编译模式概述 - 阿呆软件工作室 page and noticed you could have a lot more traffic. I have found that the key to running a website is making sure the visitors you are getting are interested in your subject matter. We can send you targeted traffic and we let you try it for free. Get over 1,000 targeted visitors per day to your website. Start your free trial: http://tdil.co/3p Unsubscribe here: http://priscilarodrigues.com.br/url/11

X 正在回复:
姓 名: 留下更多信息
性 别:
邮 件:
主 页:
Q Q:
来 自:
职 业:
评 论:


Valid HTML 4.01 Strict Valid CSS!
Copyleft.A!die Software Studio.ADSS
Power by webmaster@adintr.com