【问题标题】:How to determine sizeof farpointer against near pointer?如何根据近指针确定远指针的大小?
【发布时间】:2020-01-11 13:17:15
【问题描述】:
#include <stdio.h>
#include <stdlib.h>

int main (void) {

    int ar[3][4] = {{1,2,3,4},{5,6,7,8},{8,10,11,12}};
    printf("sizeof(ar)=%lu;sizeof(*ar)=%lu;sizeof(*ar + 0)=%lu;sizeof(*(*ar + 0))=%lu;valueof(*(*ar + 0)) = %d\n",
        sizeof(ar), sizeof(*ar), sizeof(*ar + 0), sizeof(*(*ar + 0)), *(*ar + 0));

    printf("\n\nsizeof(&ar)=%lu;sizeof(&(*ar))=%lu\n", sizeof(&ar), sizeof(&(*ar)));

    printf("\nsizeof(int)=%lu\n",sizeof(int));

    return 0;
}

输出:

    sizeof(ar)=48;
    sizeof(*ar)=16;
    sizeof(*ar + 0)=8;
    sizeof(*(*ar + 0))=4;
    valueof(*(*ar + 0)) = 1


    sizeof(&ar)=8;
sizeof(&(*ar))=8

    sizeof(int)=4

我可以假设(据此),所有地址都是 8 个字节长。但这仅适用于近指针(或偏移指针)。但是,如果我要使用的指针位于内存的另一个段中怎么办?在这种情况下,根据英特尔文档,远指针是 8 个字节的偏移量和 2 个字节的段数。也就是说,远指针大小应该是10 字节。但是没有 c 程序曾经打印过我。那么如何确定远指针和近指针呢?

【问题讨论】:

  • sizeof 运算符返回的值的类型是 size_t ,必须使用%zu 格式打印(格式说明符和参数类型不匹配会导致未定义的行为)。
  • 怎么没有远指针?我已经阅读了我当前的奔腾处理器的英特尔文档,所以显然有很远的指针,不理解这种说法。
  • 在现代系统中确实没有“远”或“近”指针之类的东西。它们是 DOS 等旧“实模式”系统的产物。在现代系统中,只有指针。
  • 大多数现代操作系统使用flat memory model 而不是segmented memory model
  • 请使用有关您正在使用的编译器、您正在使用的 CPU 以及您正在使用的操作系统的信息更新您的问题。用于分段内存(例如较旧的 Intel 80xx 处理器)的 Far 和 Near 指针需要 C 编译器中的支持,以便能够将指针指定为包含段和偏移量的 Far 指针或仅包含段内的偏移量。

标签: c pointers


【解决方案1】:

大多数现代操作系统使用flat memory model 而不是segmented memory model。这意味着所有指针都足够大,可以指向一个进程的整个virtual address space。出于这个原因,远指针不再是必需的,而且现在非常少见。

这曾经是不同的。 30 年前,常见的消费类硬件是 16 位的,这意味着远指针由 16 位段指针和 16 位偏移指针组成。这样,理论上 16 位系统可以寻址高达 4 GB 的内存(通常需要 32 位指针)。

既然您提到了您的英特尔处理器的文档,我将进一步说明 x86/x64 架构的当前状态:

在 64 位“长”模式下,段寄存器 CS、DS、ES 和 SS 被强制为 0,因此无法进行分段。只有 FS 和 GS 段寄存器仍可用于分段内存访问(即作为远指针的一部分)。但是,在 Windows 和 Linux 下,这些段寄存器对普通应用程序程序员都是不可用的。它们仅由操作系统内部使用。更多信息请参见this link

这实际上意味着,除非您想为实际的操作系统(或者可能是内核模式驱动程序)编写代码,否则您永远不必担心远指针。有可能你的编译器甚至没有实现它们。

【讨论】:

  • 所以虽然我有可用的 CS、DS、ES 和 SS 寄存器,但我不能从用户空间访问它们?或者我怎么能理解这句话,只有操作系统使用它们?在什么情况下我需要它们? (例如,我的内核驱动器需要什么样的硬件才能实际使用这些寄存器)?
  • 我相信你可以从用户空间读取所有寄存器。但是,这对于 CS、DS、ES 和 SS 段寄存器没有意义,因为它们始终为 0。但是您可以读取 FS 和 GS 寄存器。我相信 Microsoft Windows 使用 FS 指向 Process Environment Block 和 GS 指向 Thread Environment Block。但是,更改这些寄存器可能会导致崩溃。但这些是操作系统的内部结构,您不必担心。
  • 最后一个问题。如果它们被强制为空,那么如果它会以某种方式改变会发生什么(即使没有程序员可以,只是认为它会以某种方式在内部改变)。这是否意味着它会错误地取消引用指针(因为更改的 reg 中的新值),或者根本不重要? (问题是要明确操作系统的内部是否将它们用于许多进程 - 所以新值可能会损坏某些东西)
  • 我刚刚读到我关于 FS 指向进程环境块的声明似乎只在 32 位 Windows 中是正确的,但在 64 位 Windows 中则不然。这写在我在评论中链接到的维基百科文章中。
  • 假设 CS、DS、ES 和 SS 段寄存器实际上以 64 位模式存在(我的猜测是它们不存在)并且它们不会被忽略,如果你不知何故能够通过“破解”CPU来改变它们的值,那么我猜解引用任何指针都会导致所有内存访问加载错误的内存地址。
【解决方案2】:

首先您需要了解,对于任何指针或数组ar 和索引i,表达式ar[i] 完全等于*(ar + i)

有了这些知识,很容易看到您正在打印的内容:

  • sizeof(ar)这是整个数组ar的大小,也就是3 * 4元素,每个元素都是一个int。如果sizeof(int) == 4(32 位,现在很常见)那么你有3 * 4 * 4,即48 字节。

  • sizeof(*ar)sizeof(ar[0]),它是 4 int 元素数组的大小。所以大小是4 * 4,也就是16字节。

  • sizeof(*ar + 0)sizeof(ar[0] + 0)。这里ar[0] 将衰减为指向它的第一个元素的指针,向其添加零,结果是一个指针,在您的系统上似乎是8 字节,表示64 位系统。

  • sizeof(*(*ar + 0))sizeof(*(ar[0] + 0)) 这是 sizeof(ar[0][0]) 这是一个单一的元素。在您的系统上,int 的值是 4 字节。

【讨论】:

  • 是的,我了解我的程序。但这不是我要问的。我现在不知道是否存在远指针。
  • @Herdsman 那么示例程序中的大多数打印输出实际上没有任何意义。如果你的目标是一个仍然使用近端和远端指针并且有一个支持它们的编译器的旧系统,你需要做的就是声明一个显式近指针和一个显式远指针,并打印每个指针的大小。数组或其元素的大小与查看两种不同指针类型之间差异的问题无关。
  • 嗯,我没有旧的 DOS 或类似的,但 x86_64。我不知道它们不再存在,这就是为什么我知道平面内存模型。所以是的,这可能没有意义,但这不是原因。显式远指针看起来如何?
  • @Herdsman:有关声明远指针的 16 位 C 代码示例,请参见 this link
猜你喜欢
  • 2010-12-17
  • 2013-07-30
  • 2011-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-08
  • 1970-01-01
相关资源
最近更新 更多