33

谁能用一个合适的例子向我解释这些指针......以及何时使用这些指针?

4

6 回答 6

31

主要示例是 Intel X86 架构。

Intel 8086 在内部是一个 16 位处理器:它的所有寄存器都是 16 位宽。但是,地址总线是 20 位宽 (1 MiB)。这意味着您无法将整个地址保存在寄存器中,从而将您限制在前 64 kiB 中。

英特尔的解决方案是创建 16 位“段寄存器”,其内容将左移四位并添加到地址中。例如:

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 段中的任何地址,而不仅仅是± 32 kiB 距离 DX 指向的位置,就像在其他架构中所做的那样,在某些指令中具有 16 位相对偏移寻址。)

但是,请注意,您可能有两个不同值但指向相同地址的“远”指针。例如,远指针 100079B8h 指向与 12345678h 相同的位置。因此,远指针上的指针比较是无效操作:指针可能不同,但仍指向同一个地方。

这就是我认为 Mac(当时配备摩托罗拉 68000 处理器)毕竟还不错的地方,所以我错过了巨大的指针。IIRC,它们只是保证段寄存器中所有重叠位都是 0 的远指针,如第二个示例所示。

摩托罗拉的 6800 系列处理器没有这个问题,因为它们被限制为 64 kiB,当他们创建 68000 架构时,他们直接使用 32 位寄存器,因此从不需要近、远或大指针. (相反,他们的问题是只有地址的低 24 位真正重要,所以一些程序员(臭名昭著的 Apple)会使用高 8 位作为“指针标志”,当地址总线扩展到 32 位(4 GiB)时会导致问题.)

Linus Torvalds 一直坚持到 80386,它提供了一种“保护模式”,其中地址是 32 位,段寄存器是地址的高半部分,不需要添加,并且从一开始就编写 Linux 以使用 protected只有模式,没有奇怪的段的东西,这就是为什么你在 Linux 中没有近和远指针支持(以及为什么设计新架构的公司如果想要 Linux 支持就不会再回到他们身边)。他们吃了罗宾的吟游诗人,欢欣鼓舞。(耶...)

于 2010-08-26T14:11:51.960 回答
24

远指针和大指针的区别:

正如我们所知道的,默认情况下指针是near例如:int *p是一个near指针。在 16 位编译器的情况下,指针大小near为 2 个字节。而且我们已经非常清楚,编译器的大小因编译器而异;它们只存储它所引用的指针地址的偏移量。仅由偏移量组成的地址范围为 0 - 64K 字节。

Farhuge指针:

Far指针的huge大小为 4 个字节。它们存储段和指针引用的地址的偏移量。那么它们之间有什么区别呢?

远指针的限制:

我们不能通过对其应用任何算术运算来更改或修改给定远地址的段地址。也就是说,通过使用算术运算符,我们不能从一个段跳转到另一段。

如果您将远地址增加超过其偏移地址的最大值而不是增加段地址,它将以循环顺序重复其偏移地址。这也称为环绕,即如果偏移量是0xffff并且我们加 1 则它是0x0000,类似地如果我们减0x00001 则它是0xffff并且记住段中没有变化。

现在我要比较巨大的和远的指针:

1.当远指针增加或减少时,只有指针的偏移量实际上增加或减少,但在巨大指针的情况下,段和偏移值都会改变。

考虑以下示例,取自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

解释:正如我们看到的绝对地址pp1123411234*10+11230*10+41)但它们在第一种情况下不被认为是相等的,因为在far指针的情况下只比较偏移量,即它会检查是否0001==0041。这是错误的。

在大指针的情况下,比较操作是在相等的绝对地址上执行的。

  1. 远指针从不规范化,但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指针在赋值的情况下没有被规范化。但是如果对其进行算术运算,它就会被规范化。所以,his1234:1234h1is1357:0005哪个被规范化了。

    4.由于规范化,巨大指针的偏移量小于16,而远指针则不然。

    让我们举个例子来理解我想说的话:

     int main()
      {
      char far* f=(char far*)0x0000000f;
      printf("%Fp",f+0x1);
      return 0;
      }
    

输出:

    0000:0010

如果是huge指针:

      int main()
      {
      char huge* h=(char huge*)0x0000000f;
        printf("%Fp",h+0x1);
        return 0;
        }

        Output:
        0001:0000

解释:当我们将远指针增加 1 时,它将是0000:0010。当我们将巨大指针增加 1 时,它将是0001:0000因为它的偏移量不能大于 15,换句话说,它将被规范化。

于 2013-11-05T09:33:19.480 回答
15

在过去,根据 Turbo C 手册,当您的整个代码和数据适合一个段时,近指针只有 16 位。远指针由一个段和一个偏移量组成,但没有执行规范化。一个巨大的指针被自动归一化。可以想象,两个远指针可以指向内存中的同一位置但不同,而指向同一内存位置的规范化巨大指针总是相等的。

于 2010-08-26T13:40:51.917 回答
3

此答案中的所有内容仅与旧的 8086 和 80286 分段内存模型相关。

near:一个 16 位指针,可以寻址 64k 段中的任何字节

far:一个 32 位指针,包含一个段和一个偏移量。请注意,由于段可以重叠,因此两个不同的远指针可以指向同一地址。

巨大的:一个 32 位指针,其中段被“规范化”,因此没有两个远指针指向相同的地址,除非它们具有相同的值。

tee:加果酱和面包的饮料。

那会让我们回到doh oh oh oh

以及何时使用这些指针?

在 1980 年代和 90 年代,直到 32 位 Windows 无处不在,

于 2010-08-26T14:04:24.717 回答
3

在某些体系结构中,可以指向系统中每个对象的指针将比可以指向有用的事物子集的指针更大且使用起来更慢。很多人给出了与 16 位 x86 架构相关的答案。各种类型的指针在 16 位系统上很常见,尽管在 64 位系统中可能会再次出现接近/恐惧的区别,这取决于它们的实现方式(如果许多开发系统都使用 64 位指针,我不会感到惊讶)一切,尽管在许多情况下这将是非常浪费的)。

在许多程序中,很容易将内存使用划分为两类:小东西,它们加在一起的东西很少(64K 或 4GB),但会经常被访问;大东西,它们的总和可能要大得多,但不需要经常访问。当应用程序需要处理“大事物”区域中的对象的一部分时,它会将该部分复制到“小事物”区域,使用它,并在必要时将其写回。

一些程序员抱怨必须区分“近”和“远”内存,但在许多情况下,做出这样的区分可以让编译器生成更好的代码。

(注意:即使在许多 32 位系统上,也可以直接访问某些内存区域而无需额外的指令,而其他区域则不能。例如,在 68000 或 ARM 上,一个寄存器指向全局变量存储,可以直接加载该寄存器的前 32K (68000) 或 2K (ARM) 内的任何变量。获取存储在其他地方的变量将需要额外的指令来计算地址。将更常用的变量放在首选区域并让编译器知道将允许更有效的代码生成。

于 2010-08-26T16:18:56.360 回答
2

该术语用于 16 位体系结构。

在 16 位系统中,数据被划分为 64Kb 的段。每个可加载模块(程序文件、动态加载的库等)都有一个关联的数据段——最多只能存储 64Kb 的数据。

NEAR 指针是具有 16 位存储的指针,并且(仅)引用当前模块数据段中的数据。

具有超过 64Kb 数据要求的 16 位程序可以访问特殊分配器,该分配器将返回一个 FAR 指针——它是高 16 位中的数据段 id,以及低 16 位中指向该数据段的指针。

然而,更大的程序需要处理超过 64Kb 的连续数据。一个 HUGE 指针看起来与远指针完全一样——它具有 32 位存储空间——但分配器已经小心地安排了一系列数据段,具有连续的 ID,因此通过简单地递增数据段选择器,下一个 64Kb 数据块可以到达。

底层的 C 和 C++ 语言标准从未在其内存模型中正式承认这些概念——C 或 C++ 程序中的所有指针都应该是相同的大小。因此,NEAR、FAR 和 HUGE 属性是各种编译器供应商提供的扩展。

于 2010-08-26T13:52:38.567 回答