任何人都可以用适当的例子向我解释这些指针......当使用这些指针时?
答案 0 :(得分:28)
主要的例子是英特尔X86架构。
英特尔8086内部是一个16位处理器:它的所有寄存器都是16位宽。但是,地址总线为20位宽(1 MiB)。这意味着您无法在寄存器中保存整个地址,将您限制在前64 kiB。
英特尔的解决方案是创建16位“段寄存器”,其内容将向左移位4位并添加到地址。例如:
DS ("Data Segment") register: 1234 h
DX ("D eXtended") register: + 5678h
------
Actual address read: 179B8h
这创造了64 kiB段的概念。因此,“近”指针只是DX寄存器(5678h)的内容,除非DS寄存器已正确设置,否则无效,而“远”指针为32位(12345678h,DS后跟DX),总是会工作(但是因为你必须加载两个寄存器然后在完成时恢复DS寄存器,所以会慢一些。)
(正如下面的supercat说明,溢出的DX偏移会在添加到DS之前“翻转”以获取最终地址。这允许16位偏移访问任何地址64 kiB段,不仅仅是DX指向的部分是±32 kiB,在某些指令中使用16位相对偏移寻址的其他架构中也是如此。)
但是,请注意,您可能有两个“远”指针,这些指针的值不同但指向相同的地址。例如,远指针100079B8h指向与12345678h相同的位置。因此,远指针的指针比较是一个无效的操作:指针可能不同,但仍然指向同一个地方。
这是我决定Macs(当时使用摩托罗拉68000处理器)并不是那么糟糕的地方,所以我错过了巨大的指针。 IIRC,它们只是保证段寄存器中所有重叠位都为0的远指针,如第二个例子所示。
摩托罗拉在他们的6800系列处理器上没有这个问题,因为它们被限制在64千字节,当他们创建68000架构时,他们直接进入32位寄存器,因此从来没有需要近,远,或巨大的指针。 (相反,他们的问题是只有地址的底部24位实际上很重要,所以一些程序员(众所周知的Apple)会使用高8位作为“指针标志”,导致地址总线扩展到32位(4 GiB)时出现问题。)
Linus Torvalds一直坚持到80386,它提供了一个“保护模式”,地址为32位,段寄存器是地址的高半,并且不需要添加,并且从一开始就编写Linux仅使用保护模式,没有奇怪的段内容,这就是为什么你在Linux中没有远近指针支持的原因(为什么没有设计新架构的公司如果想要Linux支持就不会再回到它们)。而且他们吃了罗宾的吟游诗人,并且非常欢欣鼓舞。 (耶...)
答案 1 :(得分:20)
远与巨指针的区别:
正如我们所知,默认情况下,指针为near
,例如:int *p
是near
指针。在16位编译器的情况下,near
指针的大小是2个字节。而且我们已经非常了解编译器的大小不同编译器;它们只存储它引用的指针的地址偏移量。仅包含偏移量的地址范围为0 - 64K字节。
Far
和huge
指针:
Far
和huge
指针的大小为4个字节。它们存储指针引用的地址的段和偏移量。那么它们之间的差异是什么?
远指针的限制:
我们不能通过对其应用任何算术运算来更改或修改给定远地址的段地址。那就是通过使用算术运算符,我们不能从一个段跳到另一个段。
如果将远地址递增超出其偏移地址的最大值而不是递增段地址,它将以循环顺序重复其偏移地址。这也称为包装,即如果偏移是0xffff
并且我们加1那么它是0x0000
并且类似地如果我们将0x0000
减少1然后它是0xffff
并且记住那里该细分市场没有变化。
现在我要比较远点和远点:
1.当远指针递增或递减 ONLY 时,指针的偏移实际上会递增或递减,但如果指针偏大,则段和偏移值都会改变。
请考虑以下示例,取自HERE:
int main()
{
char far* f=(char far*)0x0000ffff;
printf("%Fp",f+0x1);
return 0;
}
然后输出是:
0000:0000
分段值没有变化。
如果是巨大的指针:
int main()
{
char huge* h=(char huge*)0x0000000f;
printf("%Fp",h+0x1);
return 0;
}
输出是:
0001:0000
这是因为递增操作不仅偏移值而且段值也改变。这意味着在far
指针的情况下段不会改变但是在huge
指针的情况下,它可以从一个指针移动分段到另一个。
2.当在远指针上使用关系运算符时,仅比较偏移量。换句话说,如果被比较的指针的段值相同,则关系运算符将仅对远指针起作用。如果发生巨大的事情就不会发生,实际上会发生绝对地址的比较。让我们在far
指针的例子的帮助下理解:
int main()
{
char far * p=(char far*)0x12340001;
char far* p1=(char far*)0x12300041;
if(p==p1)
printf("same");
else
printf("different");
return 0;
}
输出:
different
在huge
指针中:
int main()
{
char huge * p=(char huge*)0x12340001;
char huge* p1=(char huge*)0x12300041;
if(p==p1)
printf("same");
else
printf("different");
return 0;
}
输出:
same
说明:我们看到p
和p1
的绝对地址是12341
(1234*10+1
或1230*10+41
),但它们不相同第一种情况,因为在far
指针的情况下,仅比较偏移,即它将检查是否0001==0041
。这是错误的。
如果指针很大,则对相同的绝对地址执行比较操作。
远指针永远不会被标准化,但huge
指针会被标准化。规范化指针是指段中具有尽可能多的地址的指针,这意味着偏移量绝不会大于15。
假设我们有0x1234:1234
,则其标准化形式为0x1357:0004
(绝对地址为13574
)。
只有在对其执行某些算术运算时才会对一个巨大的指针进行归一化,并且在赋值期间不会对其进行归一化。
int main()
{
char huge* h=(char huge*)0x12341234;
char huge* h1=(char huge*)0x12341234;
printf("h=%Fp\nh1=%Fp",h,h1+0x1);
return 0;
}
输出:
h=1234:1234
h1=1357:0005
说明:huge
指针在赋值时没有规范化。但是如果对它执行算术运算,它将被规范化。所以,h
是1234:1234
和{{ 1}}是h1
,它是标准化的。
4.由于规范化,大指针的偏移量小于16,而远指针则不然。
让我们举个例子来理解我想说的话:
1357:0005
输出:
int main()
{
char far* f=(char far*)0x0000000f;
printf("%Fp",f+0x1);
return 0;
}
如果是 0000:0010
指针:
huge
说明:当我们将远指针递增1时,它将是 int main()
{
char huge* h=(char huge*)0x0000000f;
printf("%Fp",h+0x1);
return 0;
}
Output:
0001:0000
。当我们将大指针递增1时,它将是0000:0010
,因为它的偏移量不能大于15,换句话说它将被标准化。
答案 2 :(得分:12)
在过去,根据Turbo C手册,当您的整个代码和数据适合一个段时,近指针仅为16位。远指针由段和偏移组成,但未执行归一化。并且一个巨大的指针自动归一化。可以想象两个远指针指向内存中的相同位置但是不同,而指向相同内存位置的规范化巨大指针总是相等。
答案 3 :(得分:3)
本回答中的所有内容仅与旧的8086和80286分段内存模型相关。
near:一个16位指针,可以处理64k段中的任何字节
far:一个32位指针,包含一个段和一个偏移量。请注意,因为段可以重叠,所以两个不同的远指针可以指向相同的地址。
huge:一个32位指针,其中段被“规范化”,因此没有两个远指针指向同一地址,除非它们具有相同的值。
发球:一种带果酱和面包的饮料。那会把我们带回来哦哦哦哦
何时使用这些指针?
在20世纪80年代和90年代,直到32位Windows变得无处不在,
答案 4 :(得分:2)
该术语用于16位架构。
在16位系统中,数据被划分为64Kb段。每个可加载模块(程序文件,动态加载的库等)都有一个关联的数据段 - 最多只能存储64Kb的数据。
NEAR指针是一个具有16位存储空间的指针,在当前模块数据段中仅指数据(仅限数据)。
作为要求具有超过64Kb数据的16位程序可以访问将返回FAR指针的特殊分配器 - 这是高16位的数据段ID,以及指向该数据段的指针,位于低16位位。
更大的程序想要处理超过64Kb的连续数据。一个巨大的指针看起来就像一个远指针 - 它有32位存储 - 但是分配器已经注意安排一系列具有连续ID的数据段,这样通过简单地递增数据段选择器,下一个64Kb的数据块就可以了达到。
底层的C和C ++语言标准从未在内存模型中正式识别这些概念--C或C ++程序中的所有指针都应该具有相同的大小。因此,NEAR,FAR和HUGE属性是各种编译器供应商提供的扩展。
答案 5 :(得分:2)
在某些体系结构中,指向系统中每个对象的指针将比可以指向有用的事物子集的指针更大更慢。许多人已经给出了与16位x86架构相关的答案。各种类型的指针在16位系统上很常见,但是在64位系统中可能会出现接近/恐惧的区别,这取决于它们的实现方式(如果许多开发系统转向64位指针,我不会感到惊讶尽管事实上在许多情况下这将是非常浪费的事实。
在许多程序中,很容易将内存使用细分为两类:小东西总共可以达到相当少量的东西(64K或4GB)但是经常被访问,而更大的东西可能总计达到数量要大得多,但不需要经常访问。当一个应用程序需要处理“大事物”区域中某个对象的一部分时,它会将该部分复制到“小东西”区域,使用它,并在必要时将其写回。
一些程序员抱怨必须区分“近”和“远”内存,但在许多情况下,进行这样的区分可以使编译器生成更好的代码。
(注意:即使在许多32位系统上,某些内存区域也可以在没有额外指令的情况下直接访问,而其他区域则不能。例如,如果在68000或ARM上,则会保留一个指向全局的寄存器变量存储,可以直接加载该寄存器的前32K(68000)或2K(ARM)内的任何变量。获取存储在别处的变量将需要额外的指令来计算地址。放置更频繁使用的变量首选区域并让编译器知道将允许更有效的代码生成。