c语言中的内存模式,near far huge关键字

本文章是转载来自https://blog.csdn.net/wonengxing/article/details/6044576

编译模式是指如何在内存中放置程序代码及数据,如何分配堆栈,并确认占用的内存大小及如何存取它们,当指定内存模式(编译模式)以后,语言编译程序将按事先选择好的内存模式编译组织程序,C 语言中提供了6种编译模式,这6种模式是:微模式(Tiny),小模式(Small),中模式(Medium),紧凑模式(Compact),大模式(Large)和巨模式(Huge)。用户可以按照自己的程序大小及需要进行选择。

  所谓小程序就是指程序只有一个程序段,大小不超过64KB,缺省的码(函数)指针是near(近程指针)。所谓大程序就是指程序有多个程序段,每个程序段不超过64KB,但总程序量可超过64KB,缺省的码指针是far(远程指针)。小数据就是指数据只有一个数据段,缺省的数据指针是near。大数据就是指数据有多个数据段,缺省的数据指针是far。

C语言编译模式—微模式(Tiny)
在微模式下程序中的数据及代码均放在同一段内,即它们不超过 64KB。在微模式下代码段、堆栈段和数据段的段地址均相同,即CS=DS=SS=ES。在这个段内,码首先装入,地址最低,接着是静态变量和全局变量。然后是堆,最后是堆栈。堆栈和堆都是动态的,堆从低地址往高地址增长,堆栈从高地址往低地址增长,若两者相等,则表示空间耗完了。在微模式下,数据指针都是 near,一般小程序可采用此编译模式进行编译。还可用 DOS 中的 EXE2BIN 转换程序将.EXE 程序转换成.COM 程序。

  代码段、数据段和堆栈段均在同一段内,对它们进行寻址时,均以同一地址偏移的参考点,具有这种特点的段又称为属于同一组段(DGROUP),栈是向上生长的,即每压栈一次,栈指针SP减2,即向地址减少的方向移动,它开始的初始值指向栈底,即0xffff(64KB)。堆是向下生长的,即向增加地址的方向改变。由图中可以看出堆和栈地址相向生长,当两者未相遇时,便出现了自由空间。一般程序均是这种状态,当占用栈地址较多时,两者可能重合并覆盖部分堆空间。

C语言编译模式—小模式(Small)
在小模式下,程序中的代码放在64KB的代码段内,数据放在64KB的数据段内。在小模式下,栈段、附加数据段和数据段均指向同一地址,它们合三为一,即DS=SS=ES,指针都是near,一般程序均采用小模式编译。数据段、堆栈段和附加段为同一段组,即它们的偏移地址均以同一段地址为参考点。除了和数据/堆栈共用一个段的堆外,还有一个远堆。

C语言编译模式—中模式(Medium)
在中模式下,所有数据放在64KB的数据段内,因而数据段内使用near,代码量可以大于64KB(允许达到1MB),因而可以在不同的代码段内,代码段使用(far远程指针)。来自不同源文件的码模块放在不同的码段内。严格的讲,同一个源文件内的各函数也是放在不同的码段的。这种编译模式适用于大代码量、小数据量的大程序。还有一个远堆。

C语言编译模式—紧凑模式(Compact)
在紧凑模式下,数据量超过64KB时,可放在多个数据段中,数据段内的指针是(far)。代码量不超过64KB,在一个段内,因而代码段内指针为近程的(near)。但在该模式下,静态数据仍不能超过64KB,堆用far指针来存取。代码、静态数据、堆栈、堆各有自己的段。堆只有远堆,没有近堆。

C语言编译模式—大模式(Large)
大模式下,代码及数据均采用far指针,且都可达到1MB。静态数据、堆栈、堆同紧凑模式,代码同中模式。静态数据仍跟紧凑模式一样,不能超过64KB。

C语言编译模式—巨模式(Huge)
巨模式下,代码段及数据段均用far指针,代码分布在不同的代码段内,数据也分布在不同的数据段内,它们来自不同的源程序,大堆栈只有一个。而且静态数据大小允许超过64KB。

紧凑模式、大模式、巨模式数据区大小均允许超过64KB,即可以用数据far指针对不同数据段内的数据进行存取,它们同称为大数据存储模式。但有一点不同:紧凑模式和大模式按 C 的规定,其静态数据,即如数组、结构或其他类型的数据被定义为静态类型时,其数据量不能超过64KB,而只有巨模式才允许超过64KB。在大数据存储模式下,堆和栈分别在不同段内,多以动态数据和局部变量的形式存放,这样就不受64KB大小的限制,栈的增长不会影响堆的空间。

  无论采用哪一种编译模式,C源程序编译生成的代码和数据量都不能超过64KB,对于超过的源程序,可以视代码或数据多少将其分解成两个或多个程序分别编译。大代码量程序要选用大代码编译模式(中模式、大模式和巨模式),大数据量程序应选用大数据编译模式(紧凑模式、大模式和巨模式),这样编译生成的.obj 文件将会带给连接程序信息,将代码和数据安排在不同段内。这样生成的.exe 文件在加载时将告诉 DOS 该程序应如何装入代码段和数据段,如何初始化寄存器。这样,就可确定在不同编译模式下开辟数据区的大小,即大于64KB,或不超过64KB。

在TC中内存模式与far、near、huge等关键字又有着密切的关系。在tiny、small模式下,所有的函数定义、全局变量定义和指针变量的定义,如果没有显示的加上far、near、huge等关键字,都默认为使用了near关键字;在medium模式下,函数定义默认使用了far关键字,变量定义默认使用了near关键字;在compact模式下函数定义模式使用了near关键字,变量定义默认使用了far关键字;large模式下函数定义和变量定义模认使用了far关键字;huge模式下函数定义模认使用了far关键字,变量定义默认使用了huge关键字。

    near、far、huge关键字的真正含义是什么?这三个关键字只能用于修改函数、全局变量和指针变量,对于非指针类型的局部变量,这些关键字没有实际意义。这些关键字用于修饰函数时,huge的含义与far相同,用于指明该函数的调用方式为far调用方式,即调用时需要一个段值和一个段偏移组成的32bits调用地址,使用far call进行跳转,跳转前先压栈保存当前CS:IP。near修饰函数时,用于指明该函数的调用方式为near调用方式,调用时只需要一个16bits的近地址,即当前CS的段内偏移。

      当这三个关键字用于修饰指针时,near型指针实质上为16bits的无符号整型数,该整数给出了所指向变量在当前数据段内的偏移地址,也就是说,在使用near型指针寻址时实际上是进行如下的寻址操作:[DS:指针变量值]。对于far型的指针变量,可以寻址1MB地址空间的任意一个地方,far型指针的实质是一个32bits的整型数,高16bits为段值,低16bits为段内偏移,Turbo C中在使用far型指针时,会先将高16bits放入ES寄存器中,然后再进行如下的寻址操作:[ES:指针变量低16bits值]。对于hug型的指针变量,与far性指针变量的不同之处在于,在对far型指针变量进行+/++/-/--等操作时,far型指针变量保持段值不变(也就是高16bits),而只对段内偏移进行加减操作,所以会出现段内回绕的现象,而huge型的指针,在进行加减操作时将会自动的改变段值,不会出现段内回绕。所以给人的感觉就是huge指针能比far指针寻址更大的内存空间。

      对于局部变量,由于是创建在堆栈上,所以near、far等关键字将不具备任何意义,因为创建在堆栈上的变量的寻址方式就只有一种,即使用sp和bp维护函数堆栈,利用bp+/-一个偏移来寻址函数参数变量和局部变量。这样的寻址方式是固定而唯一的,near和far等关键字都派不上用场,这里的near和far将没有任何的实际含义。

    对于使用near、far和huge修饰的全局变量的含义也很容易理解了。near型的全局变量,被分配到了当前的数据段上,寻址这个变量只需要一个16bits的偏移量,而far型全局变量在寻址时,需要给出段值和偏移量。huge型数组可以使用大于64K的内存空间。

    far、near、huge型指针变量之间的相互转换,从小尺寸的指针到大尺寸的指针将进行自动的类型转换,转换方式为加上当前的DS形成32bits的指针。从大尺寸的指针到小尺寸的指针需要进行强制类型转换,转换的结果为只保留低16bits,但是这样俄转换没有实际的意义或者说用处不大,并且极其容易引入内存访问的错误,所以要严格避免使用。

 需要注意的是,near、far、huge三个关键字的使用,还需要内存模式的紧密配合。但并不是说tiny模式下就不能使用near、far、huge三个关键字。tiny模式下同样可以定义如下的指针:
    char far *pbuf = 0xA0000000;
    并且我能保证这个指针能够绝对正确的工作,对函数、全局变量的修饰也是如此。但是如何正确的工作,如何才是最和合理的方式,请自己思考了。基本的原理我也讲的很清楚,就不再多费唇舌。

    Turbo C中,我想最为困惑的就是内存模式了,我也是费了很多时间和精力,通过分析Turbo C的汇编代码的出的以上结论。许多朋友都对此很困惑,所以这部分重点讲了下,和大家分享。如有不正确之处,请不吝赐教,旨在抛砖引玉。tcc编译汇编代码的方法为:tcc -c -mt -S filename.c,-c指明compile only,-mx用于指定内存模式,-S指明生成汇编代码,如果大家有兴趣可以尝试使用这个方法分析tcc编译结果的汇编代码,从而更加深刻的理解C与汇编的关系。

    当我们在编写、制作并向用户提供自己的库文件时,也需要注意内存模式的匹配,否则在进行链接时会存在问题。一个较为简单的方法就是向用户提供全套内存模式的库文件,这也是Turbo C的ANSI C库的做法,前文已经提到。如果不想提供多个内存模式的库文件,可以对程序中每个函数、全局变量和指针变量进行显式的类型声明,以精确定义每个变量的类型。

猜你喜欢

转载自www.cnblogs.com/bloomingFlower/p/9062325.html