【问题标题】:size of pointers and architecture指针和架构的大小
【发布时间】:2019-10-06 02:40:55
【问题描述】:

通过在普通台式 PC 上运行一个简单的 C++ 程序进行基本测试,假设任何类型的指针(包括指向函数的指针)的大小等于目标架构位似乎是合理的?

例如:在 32 位架构中 -> 4 字节和在 64 位架构中 -> 8 字节。

但我记得读过,一般情况下不是这样的!

所以我想知道这种情况会是什么?

  • 与指针大小相比,指向数据类型的指针大小相等 到其他数据类型
  • 与指针大小相比,指向数据类型的指针大小相等 到函数
  • 目标架构的指针大小相等

【问题讨论】:

  • 在某些架构上,并非所有指针的大小都相同。显然,两个不同的大小不能都等于相同的“架构大小”。 PIC 通常具有 8 位数据指针和 14 位函数指针。 16 位 x86 有 16 位近指针和 32 位远指针。
  • 如果您的计算机体系结构使用银行交换内存,则指针可能由两部分组成:银行和进入银行的地址。如果您的架构使用分段内存,则“远”地址可能由段和偏移量组成,而“近”地址可能仅具有偏移量。函数指针的大小可能与数据指针不同,成员指针可能具有重要的实现细节,使其可能比架构大 x2 或 x3..
  • 这只是要求“不,你不能假设”吗?还是假设失效的每种情况的开放式列表?还是什么?
  • @Useless 好吧,这个问题本身很开放,但答案很简单。 “如果正确性取决于它,就不要假设它。”
  • “目标架构位”你如何定义它?

标签: c++ c assembly portability


【解决方案1】:

目标架构“位”表示寄存器大小。前任。 Intel 8051 是 8 位的,在 8 位寄存器上运行,但(外部)RAM 和(外部)ROM 使用 16 位值访问。

【讨论】:

  • 这应该是个评论。
  • @MamCieNaHita 你是对的,我现在才想起 AVR-8 也是如此。但从其他 cmets 看来,它似乎比这更复杂!谢谢你提醒我。
  • @fuz 问题是问“目标架构位数与指针大小不同的情况是什么”。答案很好,恕我直言,这个问题太开放了。
【解决方案2】:

对于正确性,你不能假设任何事情。你必须检查并准备好应对奇怪的情况。

作为一个一般经验法则,这是一个合理默认的假设

但这并不是普遍正确的。例如,参见X32 ABI,它在 64 位架构上使用 32 位指针来节省一点内存和缓存占用空间。 AArch64 上的 ILP32 ABI 也是如此。

因此,为了猜测内存使用情况,您可以使用您的假设,而且通常是正确的。

【讨论】:

  • PAE 与任何现有 C++ 实现上的 C++ 编程无关。它不会增加虚拟地址空间的大小,只会增加物理地址空间。而且它仅在启用分页时才有效,因此在一个假设的独立 C++ 程序中,在禁用分页的情况下运行,它在处理超过 4GB 的物理内存时无济于事。
  • @JesperJuhl 有太多的架构(例如,基指针和位移指针)和语言系统(例如 LISP),其中指向不同类型的指针具有不同的长度。如果您包含指向函数的指针,就像 OP 明确所做的那样,在某些情况下,指针的大小取决于传递的参数数量和函数的返回值。对于任何可移植性意图来说,这都是一个糟糕的假设,并且可以在您意识到问题之前让您深入了解。你的答案的最后两句应该是前两句。
  • @mpez0 我知道。是的,当然,这对于可移植性来说是一个糟糕的假设,这就是为什么我明确地说这是一个可以猜测内存使用的假设,但对正确性毫无用处。
  • @mpez0 - “你的答案的最后两句应该是前两句”——现在更好了吗?
  • @JesperJuhl 是的,更好,谢谢。如果您在最近的 x86 架构上将除 Algol 家族语言之外的任何东西视为“奇怪”,我只会将不同大小的指针视为“奇怪”。然而,这是一个相当普遍的观点和一组默认假设。它会起作用,直到它不起作用,然后清理将是一个很大的谜。
【解决方案3】:

可以合理地假设任何类型的指针(包括指向函数的指针)的大小通常都等于目标架构位

视情况而定。如果您的目标是快速估计内存消耗,那么它就足够了。

(包括指向函数的指针)

但这里有一个重要的评论。尽管大多数指针的大小相同,但函数指针可能不同。不保证 void* 将能够保存函数指针。至少,这对 C 来说是正确的。我不了解 C++。

所以我想知道如果有这种情况会是什么?

它不同的原因可能有很多。如果你的程序的正确性取决于这个大小,那么做这样的假设是绝对不行的。而是检查一下。应该不难。

您可以使用此宏在 C 中编译时检查此类内容:

#include <assert.h>
static_assert(sizeof(void*) == 4, "Pointers are assumed to be exactly 4 bytes");

编译时,会出现错误信息:

$ gcc main.c 
In file included from main.c:1:
main.c:2:1: error: static assertion failed: "Pointers are assumed to be exactly 4 bytes"
 static_assert(sizeof(void*) == 4, "Pointers are assumed to be exactly 4 bytes");
 ^~~~~~~~~~~~~

如果您使用 C++,则可以跳过 #include &lt;assert.h&gt;,因为 static_assert 是 C++ 中的关键字。 (并且你可以在 C 中使用关键字_Static_assert,但它看起来很难看,所以请改用包含和宏。)

由于这两行代码非常容易包含在您的代码中,如果您的程序在错误的指针大小下无法正常工作,没有理由不这样做。

【讨论】:

    【解决方案4】:

    不,假设是不合理的。做出这种假设可能会导致错误。

    C 或 C++ 中指针(和整数类型)的大小最终由 C 或 C++ 实现决定。普通的 C 或 C++ 实现受其所针对的体系结构和操作系统的严重影响,但它们可能会出于执行速度以外的原因选择其类型的大小,例如支持较低内存使用的目标(较小的指针意味着较少的内存使用具有大量指针的程序),支持未编写为完全可移植到任何类型大小的代码,或者支持更轻松地使用大整数。

    我见过一个针对 64 位系统但提供 32 位指针的编译器,用于构建内存使用量较小的程序。 (据观察,指针的大小是内存消耗的一个重要因素,这是由于使用了许多具有许多连接的结构和使用指针的引用。)在假设指针大小等于 64 位寄存器的情况下编写的源代码尺寸会损坏。

    【讨论】:

    【解决方案5】:

    这是不正确的,例如 DOS 指针(16 位)可能很远(seg+ofs)。

    但是,对于通常的目标(Windows、OSX、Linux、Android、iOS),它是正确的。因为它们都使用依赖分页的扁平化编程模型。

    理论上,您也可以拥有在 x64 中仅使用低 32 位的系统。一个示例是没有 LARGEADDRESSAWARE 链接的 Windows 可执行文件。然而,这是为了帮助程序员在切换到 x64 时避免错误。指针被截断为 32 位,但它们仍然是 64 位。

    在 x64 操作系统中,这个假设总是正确的,因为平面模式是唯一有效的模式。 CPU 中的长模式强制 GDT 条目为 64 位平面。

    其中还提到了一个x32 ABI,我相信它是基于相同的分页技术,强制将所有指针映射到较低的4gb。但是,这必须基于与 Windows 相同的理论。在 x64 中,您只能使用平面模式。

    在 32 位保护模式下,您可以拥有最多 48 位的指针。 (分段模式)。你也可以有呼叫门。但是,没有操作系统使用该模式。

    【讨论】:

    • 每个 32 位 x86 操作系统都使用 x86 32 位保护模式。但是(几乎?)他们都使用平面内存模型,我认为这是您要提出的观点。无论如何,是的,seg:off "far" 指针在保护模式下将占用 6 个字节,但您仍然只有 4GB 的实际可寻址内存。段基址 + 偏移量产生一个 32 位线性地址。禁用分页后,它是一个 32 位物理地址。启用分页后,它是一个 32 位的虚拟地址。通过将 32 位虚拟地址转换为 36 位物理地址,PAE 可以让各个进程同时使用单独的 4GB 物理内存。
    • Linux x32 ABI 和其他 ILP32 ABI 通常不需要分页。理论上,您可以拥有一个操作系统,它可以在物理地址空间的低 32 位内的不同物理地址加载与位置无关的代码。
    • Nitpick:长模式忽略由非 FS/GS 段选择的 GDT 条目中的基数/限制,而不是要求它们为 0 / -1。并且“映射”是确保所有指针都在低 4GB 中的错误词,该措辞似乎暗示映射到低 4GB 物理内存的任意虚拟地址。 (顺便说一句,它实际上是低 2GB 的虚拟地址空间,因此 32 位绝对指针的零扩展和符号扩展都是有效的。例如 mov edi, array(零扩展立即数)或 add rax, [array + rcx](符号扩展 disp32)都可以用于静态地址。
    【解决方案6】:

    假设任何类型的指针(包括指向函数的指针)的大小通常等于目标体系结构位是合理的吗?

    这可能是合理的,但它并不可靠。所以我猜答案是“不,除非你已经知道答案是肯定的(并且不担心可移植性)”

    可能:

    • 系统可以有不同的寄存器大小,并为数据和寻址使用不同的底层宽度:对于这样的系统,“目标架构位”甚至意味着什么并不明显,因此您必须选择特定的 ABI(并且一次对于该 ABI,您已经完成了您知道答案的工作。

    • 系统可能支持不同的指针模型,例如旧的nearfarhuge指针;在这种情况下,您需要知道您的代码正在以哪种模式编译(然后您就知道该模式的答案)

    • 系统可能支持不同的指针大小,例如已经提到的 X32 ABI,或here 中描述的其他流行的 64 位数据模型之一

    最后,这个假设没有明显的好处,因为您可以直接使用sizeof(T) 来处理您感兴趣的任何T

    如果要在整数和指针之间进行转换,请使用intptr_t。如果要将整数和指针存储在同一空间中,只需使用union

    【讨论】:

      【解决方案7】:

      假设任何类型的指针(包括指向函数的指针)的一般大小都等于目标架构位是合理的吗?

      如果您查看当前生产的所有类型的 CPU(包括微控制器),我会说不。

      极端的反例是在同一程序中使用两种不同指针大小的架构:

      x86,16 位

      在 MS-DOS 和 16 位 Windows 中,“普通”程序同时使用 16 位和 32 位指针。

      x86,32 位分段

      只有少数几个鲜为人知的操作系统使用这种内存模型。

      程序通常同时使用 32 位和 48 位指针。

      STM8A

      这款现代汽车 8 位 CPU 使用 16 位和 24 位指针。当然,两者都在同一个程序中。

      AVR 微型系列

      RAM 使用 8 位指针寻址,Flash 使用 16 位指针寻址。

      (不过,据我所知,AVR tiny 无法用 C++ 编程。)

      【讨论】:

      • GCC 有一个 AVR 后端;我假设您至少可以使用std:: 算法编译一个函数,如果不是进行动态分配的容器。
      • 如果我没记错的话,8086 并没有真正的 32 位指针,它允许 4 GB 的地址空间。它在分段地址空间中具有 24 位 FAR 指针,其中每个偏移量寻址 64K 字节(与 NEAR 指针相同),段寄存器允许段从内存中的任何 256 字节边界开始,给出 1 MByte地址空间。
      • @jamesqf x86 上的段寄存器是 16 位宽,而不是 8 位。所以一个 16 位的 far 指针的大小是 32 位。在“实模式”(8086)中,段可以在任何 16 字节边界上对齐。而分段地址 0x7C0:0 和 0x0:7C00 指向 RAM 中的同一个字节,但它们在用作代码指针时具有不同的含义。
      • @Martin Rosenau:是的。也许我的评论不清楚:段:偏移寄存器对占用 32 位,但由于它们的实现方式,它们只允许 24 位地址空间。 (如果有内存,您必须在汇编级别分别操作段和偏移寄存器。)
      • @jamesqf 这取决于。在“实模式”(8086 模式)中,可以在内存中访问 (2^20)+(2^16)-16 data 字节。这意味着实际上有少于 21 个地址位。假设,一个 8086 有 2^32 个 code 地址,不能 被替换。这意味着这 2^32 个地址中的每一个都有不同的含义,不能被另一个地址代替!这意味着代码指针在 8086 上实际上是 32 位宽。在 80386 上运行的 16 位代码可以寻址超过 2^29 个数据字节,因此使用 16:16 分段寻址的有效地址宽度为 30 位。
      【解决方案8】:

      从历史上看,在微型计算机和微控制器上,指针通常比通用寄存器宽,因此 CPU 可以寻址足够的内存并且仍然适合晶体管预算。大多数 8 位 CPU(例如 8080、Z80 或 6502)都有 16 位地址。

      今天,不匹配更可能是因为应用程序不需要数 GB 的数据,因此在每个指针上节省 4 个字节的内存是一个胜利。

      C 和 C++ 都提供了单独的size_tuintptr_toff_t 类型,表示可能的最大对象大小(如果内存模型不平坦,它可能小于指针的大小),一个整数类型足够宽以容纳指针和文件偏移量(通常比内存中允许的最大对象宽)。 size_t(无符号)或ptrdiff_t(有符号)是获取本机字长的最便携方式。此外,POSIX 保证系统编译器有一些标志,这意味着 long 可以保存任何这些,但您不能总是这样假设。

      【讨论】:

      • 您有什么原因遗漏了签名intptr_t?无论如何,值得指出的是[u]intptr_t 可以保存任何指针,而size_t 只需要保存最大对象大小。在没有平面内存模型的机器上,这些很容易是不同的宽度。例如在 x86-16 上可以使用远指针,uintptr_t 必须是 32 位,但 size_t 可以是 16 位。
      • (请注意,大多数实现将对象大小限制为 SIZE_MAX/2 或更小,因此 ptrdiff_t 不能溢出 char 数组。)Why is the maximum size of an array "too large"?
      • off_t 用于 文件 大小/位置。在纯 32 位系统上,它可以而且通常是 64 位,在这里提及它是零意义的。此外,您提到的任何类型都不能保证找到最大寄存器宽度:64 位架构上的现代 ILP32 ABI 通常具有 32 位 size_tuintptr_tptrdiff_t。因此,如果您使用它来确定机器是否具有高效的long long / uint64_t,例如,您将错误地排除 x86-64 上的 x32 和 AArch64 上的 ILP32。您还可以检查#ifdef __SIZEOF_INT128__,因为 GCC 在 64 位上定义了它。
      • @PeterCordes 我不认为我们不同意。我只提到了uintptr_t,因为它与签名对应的宽度完全相同,而其他两种类型都是无符号的。
      • @PeterCordes uintptr_t 不必是任何东西,它是可选类型
      【解决方案9】:

      通常指针大小在 16 位系统上为 2,在 24 位系统上为 3,在 32 位系统上为 4,在 64 位系统上为 8。这取决于ABI 和C 实现。 AMD 有long and legacy 模式,也有differences between AMD64 and Intel64 for Assembly language 程序员,但这些对于高级语言是隐藏的。

      C/C++ 代码的任何问题都可能是由于糟糕的编程习惯和忽略编译器警告造成的。请参阅:“20 issues of porting C++ code to the 64-bit platform”。

      另见:“Can pointers be of different sizes?”和LRiO's answer

      ...您询问的是 C++ 及其兼容的实现,而不是某些特定的物理机器。为了证明它,我必须引用整个标准,但简单的事实是它不能保证任何 T 的 sizeof(T*) 的结果,并且(作为推论) 不保证 sizeof(T1*) == sizeof(T2*) 对于任何 T1 和 T2)。

      注意:其中answered by JeremyP,C99 第 6.3.2.3 节第 8 小节:

      指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针,然后再返回;结果应与原始指针比较。如果转换后的指针用于调用类型与指向的类型不兼容的函数,则行为未定义。

      在 GCC 中,您可以通过使用内置函数来避免错误假设:“Object Size Checking Built-in Functions”:

      内置函数:size_t __builtin_object_size(const void * ptr, int type)

      是一个内置构造,​​它返回从 ptr 到 ptr 指针指向的对象末尾的恒定字节数(如果在编译时已知)。为了确定动态分配对象的大小,该函数依赖于调用的分配函数来获取要使用 alloc_size 属性声明的存储(请参阅通用函数属性)。 __builtin_object_size 从不评估其参数的副作用。如果它们有任何副作用,它返回 (size_t) -1 类型 0 或 1 和 (size_t) 0 类型 2 或 3。如果有多个对象 ptr 可以指向并且它们在编译时都是已知的,如果 type & 2 为 0,则返回的数字是这些对象中剩余字节数的最大值,如果非零,则返回最小值。如果无法在编译时确定 ptr 指向哪些对象,则 __builtin_object_size 应为类型 0 或 1 返回 (size_t) -1,为类型 2 或 3 返回 (size_t) 0。

      【讨论】:

      • Intel64 和 AMD64 之间的差异非常小,与指针宽度的讨论完全无关。它们几乎仅限于一些内核系统管理差异。所有 x86-64 的正常计算都是相同的;这就是为什么我们不需要为 Intel 和 AMD CPU 提供单独的二进制文件。
      • 您假设 CHAR_BIT 定义为 8。24 位系统可能是具有 24 位字可寻址内存的 DSP,因此 char 可能也是 24 位.因此 sizeof() 一切 = 1。
      • 你在here 或我链接的问答中说了什么?
      • 说了什么?我没有评论或回答那个相关的问题,不知道你在说什么。也许您的意思是 24 位系统可能没有 24 位指针;这当然是可能的,允许存在某些或所有类型的指针比其 24 位 char/int 更宽的 C++ 实现。但我的意思是一个“普通”的 24 位 DSP,它可能没有带有 3 字节“字”的字节可寻址内存,所以一个普通的 C++ 实现可能会有sizeof(void*) = sizeof(int*) = sizeof(char) = sizeof(int) = 1
      猜你喜欢
      • 2013-02-26
      • 1970-01-01
      • 1970-01-01
      • 2014-07-08
      • 2017-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多