far指针与near指针
win16、win32相关知识:
运行在16位CPU(80826微处理器)微机上的Windows16,对于内存按照“分段内存模式”(Segment Memory Mode)进行使用.在DOS下(实模式)地址是分段的,每一段的长度为64K字节,刚好是16位(二进制的十六位)。
16位CPU(主要指80386和80486)内部数组总线为16位,地址总线为24位。这就意味着一个字(16bit)存不下一个地址,要用到两个字。习惯把高位字称为段地址,低位字称为偏移地址。
Windows 95开始的32位机上,支持32位平面内存模式,为Win32写的程序使用32位的线性地址空间。
只有在win16上,才会设计到near、far指针的概念。在win32上,并没有此区别。
程序运行时,OS会给程序分配几个内存段供程序使用,主要的有代码段(CS)、数据段(DS)、堆栈段(SS)。根据不同的编译模式这几个段可能重合。
当指针指向的地址与数据段的段地址相同(即处于数据段内),这时可以不记录段地址而仅记录偏移地址(只需16bit)。这种指针称为近指针(near指针)。
当指针指向的地址与数据段的段地址不同(即处于数据段外),这里不仅要记录偏移地址还要记录段地址(共需32bit)。这种指针称为远指针(far指针)。
在DOS下(实模式)地址是分段的,每一段的长度为64K字节,刚好是16位(二进制的十六位)。
near指针的长度是16位的,所以可指向的地址范围是64K字节,通常说near指针的寻址范围是64K。
far指针的长度是32位,含有一个16位的基地址和16位的偏移量,将基地址乘以16后再与偏移量相加,(所以实际上far指针是20位的长度)即可得到far指针的1M字节的偏移量。所以far指针的寻址范围是1M字节,超过了一个段64K的容量。例如一个far指针的段地址为0x7000,偏移量为0x1244,则该指针指向地址0x71224,如果一个far指针的段地址是0x7122,偏移量为0x0004,则该指针也指向地址0x71224
存储属性:
near指针:16位段内偏移地址
far指针:16位段地址+16位段内偏移地址
huge指针:32位规格化的具有唯一性的内存地址
C语言的存贮属性由六种编译模式决定(参见TC集成环境菜单中的option->compiler->model选项),默认的编译模式为small,在该编译模式下,指针的默认属性为near。所以far指针要显式指定。
near关键字:
near关键字创建一个指向可寻址内存低端部分的目标指针,是16位指针,依赖一个段地址寄存器,这些指针占用内存的单一字节,能够指向的内存单元被限制到256个位置,通常是在 0x0000~0x00ff范围中,指针变量就是位移量,利用段地址寄存器+指针来寻址,所以有64K之限制。对于数据指针,在微小和中编译模式下产生的数据指针是近指针,因为此时只有一个不超过64K 字节的数据段。对于码(即函数指针)指针,在微小和紧凑编译模式下产生的码指针是近指针,因为此时只一个不超过 64K字节的码段。为了形成32位 的完整地址,编译程序一般是反近指针与程序的数据段的段地址组合起来。虽然近指针占用空间最小,执行速度最快,但它有一个严格的限制,只能存取程序的数据段内的数据。如果在小模式下编译一个程序,而这个程序企图增量一个近指针使之 超过第65536个字节,则这个近指针就会复位到0. 如:
char _near * p=(char _near*)0xffff;
p++;
由于近指针的这个严重限制,所有在比较大或比较复杂的程序中, 都无法使用。
far关键字:
far关键字创建一个能够指向内存中任何数据的指针,是32位指针,不但有16位的位移量,还有16位的段地址,但此指针有个缺陷,增量时只加到位移部分,一旦16位的位移量超过了FFFF就会回到这个段地址的初始。例如一个far指针的段地址为0x7000,偏移量为0x1244,则该指针指向地址0x71224. far指针要显式指定,工作起来要慢一些,因为每次访问一个far指针时,都要将数据段或程序段的数据交换出来。另外,far指针的运算也比较反常,例如上面讲到的far指针指向同一个地址,但是比较的结果却不相同。许多库函数就是显式地指定为far函数的形式。far指针通常和farmalloc()这样的内存分配函数一起使用。每次使用远指针时都需要重新装载段寄存器,这显然会降低速 度。应该注意:尽管远指针可以寻址内存中的任一单元,但它所寻址的目标也不能超过64K字节。这是因为,远指针在增量或减量之类的 算术运时,也只是偏移量部分参与运算,而段地址保持不变。因此,当远指针增量或减量到超过64K字节段边界时就出错。
例如:
char far * fp=(char far *)0xb800ffff;
fp++; //在指针加1以后,fp将指向B800:0000,而不是所希望的 C800:0000。
什么时候使用far指针?
当使用小代码或小数据存储模式时,不能编译一个有很多代码或数据的程序。因为在64K的一个段中,不能放下所有的代码与数据。为了解决这个问题,需要指定以far函数或far指针来使用这部分的空间(64K以外的空间)。许多库函数就是显式地指定为far函数的形式。far指针通常和farmalloc()这样的内存分配函数一起使用。
此外,在进行指针比较时,far指针还会引起另外一些问题。far指针是由偏移量和段地址这样一对16位数来表示的,对于某一实际内 存地址,far指针不是唯一的,例如,far指针1234:0005、1230:0045、1200:0345、1000:2345、0900:9345等都是代表实际地址12345,这样 会引起许多麻烦。
第一,为了便于与“空”(NULL)指针(0000: 0000)进行比较,当 关系操作符“==”和“!=”用于对far 指针进行比较时,比较的是全部32位。否则,如果只比较16位偏移量,那么任何偏移量为0 的指针,都将是“空”(NULL)指针,这显然不符合一般使用要求。但在进行这32位比较时,不是按20位实际地址来比较,而是把段地址和偏移量当 作一个32位无符号长整数来比较。对于上面这个例子,假设这些指针 分别叫作a、b、c、d、e,尽管这5个far指针指向的都是同一内存单元,但下列表达式运算的结果却都为“假”,从而得出错误的结论:
if(a==b)....
if(b==c)....
if(c==d)....
if(d==e)....
if(a==c)....
if(a==d)....
第二,当用“>”、“>=”,“<”和“<=”关系操作符对指针进 行比较操作时,比较的仅仅是偏移量部分,即按无符号的16位整数进行比较。因此,对于上面这个例子,下列表达式运算的结果将都为“真”,也得出错误的结论:
if(e>d)....
if(d>c)....
if(c>b)....
if(b>a)....
if(e>a)....
huge关键字:
huge指针,只有巨指针才是一般C语言教科书上所说的指针,它像远指针也占4个字节。与远指针的显著差别是:当增量或减量超过64K字节段边界时,巨指针会自动修正段基址的值。因此,巨指针不但可以寻址内存中的任一区域,而且所寻址的数据目标可以超过64K字节。例如:
char huge *hp=(char huge *)0xb800ffff;
hp++; //在指针加1后,hp将指向C800:0000。
但是,巨指针总是比较慢的,因为编译必须生成一小段程序对指针进行32位而不是16位的加减运算,此外,由于huge指针是规则化指针,每一个实际内存地址只一个 huge指针,所有在指针比较时不会产生错误。
based关键字:
based指针,像近指针一样,基指针只占两个字节,这两个字节是地址的偏移量。像远指针一样,基指针可以寻址内存中的任一区域。近指针的段地址隐含地取自程序的数据段,远指针的段地址取自指针本身,基指针的段地址取法以及基指针的许多技术和应用问题.
huge指针与far指针的区别:
huge指针是经过规范过的,可以直接比较大小。不过由于要处理后进行比较,所以运算速度较慢,far指针不能直接比较大小,但由于只比较偏移量,所以far指针的运算速度较快,可以根据需要选用
各类指针之间的转换:
far指针可以强制转换为near 指针,做法很简单,抛掉段地址只 保留偏移量。near指针也可以转换为far指针,Turbo C的做法是从相 应的段寄存器中取得段地址。
far指针有时也需要转换为huge指针,以便对指针进行比较或做
其它操作。一种方法是通过下面这样一个规则化函数:
void normalize(void far **p)
{
*p = (void far*)(((long)*p ^ 0xffff000f) +
(((long)*p ^ 0x0000fff0) << 12));
}