【问题标题】:How to detect machine word size in C/C++?如何检测 C/C++ 中的机器字长?
【发布时间】:2016-06-20 23:32:49
【问题描述】:

是否有一种或多或少可靠的方法(不一定完美)来检测我正在为其编译的目标架构机器字长

机器字大小我指的是整数累加器寄存器的大小(例如 x86 上的 EAX、x86_64 上的 RAX 等,不是流扩展、段或浮动-点寄存器)。

该标准似乎没有提供“机器字”数据类型。所以我不是在寻找一种 100% 可移植的方式,只是在最常见的情况下工作(Intel x86 Pentium+、ARM、MIPS、PPC - 即基于寄存器的现代商品处理器) .

size_tuintptr_t 听起来像是不错的候选者(实际上与我测试的所有地方的寄存器大小相匹配)但当然是其他东西,因此不能保证总是这样做,正如 Is size_t the word size 中已经描述的那样。

上下文

假设我正在对一个连续数据块实施散列循环。生成的哈希值取决于编译器是可以的,只有速度很重要。

示例:http://rextester.com/VSANH87912

在 Windows 上的测试表明,64 位块中的散列在 64 位模式和 32 位模式下的 32 位中更快:

64-bit mode
int64: 55 ms
int32: 111 ms

32-bit mode
int64: 252 ms
int32: 158 ms

【问题讨论】:

  • Is size_t the word size?的可能重复
  • 您仍然可以混合来自编译器和具有特定宏的体系结构的信息。
  • @stark sizeof(long) 在 64 位 Windows 数据模型上为 4(32 位)。
  • 重新上下文 - 问题很清楚 - 我如何确定机器字长。然后可以将答案应用于多种不同的上下文。
  • 您为什么需要知道?你想用这些信息做什么?如果我在某个平台上告诉您一个不正确的字长,它将如何影响您尝试做的任何事情?

标签: c++ c cpu-registers


【解决方案1】:

由于 C 和 C++ 语言刻意抽象出机器字长等考虑因素,因此任何方法都不太可能 100% 可靠。但是,有各种 int_fastXX_t 类型可以帮助您推断大小。例如,这个简单的 C++ 程序:

#include <iostream>
#include <cstdint>

#define SHOW(x) std::cout << # x " = " << x << '\n'

int main()
{
    SHOW(sizeof(int_fast8_t));
    SHOW(sizeof(int_fast16_t));
    SHOW(sizeof(int_fast32_t));
    SHOW(sizeof(int_fast64_t));
}

在我的 64 位 Linux 机器上使用 gcc 5.3.1 版产生这个结果:

sizeof(int_fast8_t) = 1
sizeof(int_fast16_t) = 8
sizeof(int_fast32_t) = 8
sizeof(int_fast64_t) = 8

这表明发现寄存器大小的一种方法可能是寻找所需大小(例如 16 位值的 2 个字节)和相应的 int_fastXX_t 大小之间的最大差异,并使用 @ 的大小987654326@作为寄存器大小。

更多结果

Windows 7, gcc 4.9.3 在 64 位机器上 Cygwin 下:同上

64 位计算机上的 Windows 7、Visual Studio 2013 (v 12.0):

sizeof(int_fast8_t) = 1
sizeof(int_fast16_t) = 4
sizeof(int_fast32_t) = 4
sizeof(int_fast64_t) = 8

Linux,32 位 ARM 上的 gcc 4.6.3 和 Linux,32 位 Atom 上的 gcc 5.3.1:

sizeof(int_fast8_t) = 1
sizeof(int_fast16_t) = 4
sizeof(int_fast32_t) = 4
sizeof(int_fast64_t) = 8

【讨论】:

  • 不错的建议。太糟糕了,这在 Windows 中不起作用(在 32 位和 64 位平台上打印相同的值)。
  • @rustyx:你是对的。我已经添加到我的答案中以表明这一点。
  • 您的结果可能是您使用的编译器的结果。可以在 64 位机器上使用 32 位编译器。结果将运行。
  • @RobertJacobs:结果完全是使用哪个编译器的结果,所以它更像是编译器测试而不是 CPU 测试。
  • @Edward 这就是我在 Windows 7 Visual Studio 2013 测试中询问您的编译器设置的原因。结果是 32 位还是 64 位可执行文件?
【解决方案2】:

我想你想要

sizeof(size_t) 应该是索引的大小。 IE。 ar[index]

32 bit machine

char 1
int 4
long 4
long long 8
size_t 4

64 bit machine

char 1
int 4
long 8
long long 8
size_t 8

这可能更复杂,因为 32 位编译器在 64 位机器上运行。他们的输出是 32,即使机器有更多的能力。

我在下面添加了 windows 编译器

Visual Studio 2012 compiled win32

char 1
int 4
long 4
long long 8
size_t 4

Visual Studio 2012 compiled x64

char 1
int 4
long 4
long long 8
size_t 8

【讨论】:

  • 那么我将使用哪一个作为机器字长?
  • 我会选择 size_t 因为数组访问本质上是一个寄存器操作。
  • size_t 应该是“大到足以包含任何对象的字节大小”[support.types]/6。它与数组访问无关。
  • @RobertJacobs - 是的,malloc 接受size_t 类型的参数。那是因为size_t 可以表示任何对象的大小;这就是标准的要求,这就是我引用标准的原因。数组索引是关于 pointer 范围的;如果您要索引到 char 数组,您的索引类型最好足够大以容纳 char 数组的最大大小,即使单个寄存器不足以容纳这样的索引值。跨度>
  • 一旦您在 64 位硬件上运行 32 位编译器,您就必须问“问题是什么?”和“为什么?”。
【解决方案3】:

即使在机器架构中,一个单词也可能是多个东西。 AFAIK 你有不同的硬件相关数量:

  • 字符:一般来说,它是可以与内存交换的最小元素 - 现在几乎所有地方都是 8 位,但在某些较旧的架构上曾经是 6 位(80 年代早期的 CDC)
  • 整数:整数寄存器(例如 x86 上的 EAX)。恕我直言,可接受的近似值是sizeof(int)
  • 地址:架构上可以解决的问题。恕我直言,可接受的近似值是sizeof(uintptr_t)
  • 不说浮点数...

让我们做一些历史:

Machine class     |   character    |  integer    | address
-----------------------------------------------------------
old CDC           |     6 bits     |    60 bits  |  ?
8086              |     8 bits     |    16 bits  |  2x16 bits(*)
80x86 (x >= 3)    |     8 bits     |    32 bits  |  32 bits
64bits machines   |     8 bits     |    32 bits  |  64 bits    
                  |                |             |
general case(**)  |     8 bits     | sizeof(int) | sizeof(uintptr_t)

(*) 这是一种特殊的寻址模式,其中高位字仅移动 8 位以产生 20 位地址 - 但远指针用于位 32 位长

(**) uintptr_t 在旧架构上没有多大意义,因为编译器(当它们存在时)不支持该类型。但是,如果在它们上移植了一个像样的编译器,我认为值就是那样。

但是注意:类型是由编译器定义的,而不是架构。这意味着,如果您在 64 位机器上找到 8 位编译器,您可能会得到 sizeof(int) = 16sizeof(uintptr_t) = 16。因此,只有当您使用适合架构的编译器 时,上述内容才有意义...

【讨论】:

  • sizeof(int) 在 x86_64 上不是可接受的近似值。它仍然是 4,而 RAX 是 8 个字节。
  • @rustyx x86_64 是具有固定整数累加寄存器大小的单一架构。你在说什么 x86_64 的近似值? :D
【解决方案4】:

选择 sizeof(int *) * CHAR_BIT 以获取机器架构大小(以位为单位)。

原因是架构可能是分段的,size_t 给出了单个对象的最大大小(这可能是您想要的,但与机器的架构自然位大小不同)。如果 CHAR_BIT 为 8 但底层字节不是 8 位,则字符和空指针可能有额外的位以允许它们寻址 8 位单元。 int * 最不可能有这样的填充。但是,CHAR_BIT 可能不是 8。

【讨论】:

  • 在我看来,答案很好且正确。有些平台的每个字符超过 8 位,因此,正如您所注意到的,CHAR_BIT 是必须的。
  • 如果我错了,请纠正我,但指针的大小和数据寄存器的大小是不同的(不管CHAR_BIT,顺便说一句)。例如,您可能(一般而言)有 16 位地址空间(无需分段即可寻址...)和 8 位寄存器。仅提及一些众所周知的事情,您可以考虑 8008(以及许多其他现代微控制器)。事情甚至可能更复杂(8080 和后续产品)。现代(OP 明确提到)PPC(具有 e200z7 内核)例如是具有 64 位通用寄存器的 32 位 CPU。
  • 答案不正确,因为在众多 8 位 MCU 中的 任何 上都不是这样:AVR、PIC、HC08、R8C、8051、Z80。 ..至少前4个还在量产中。
【解决方案5】:

我会给你正确的答案来回答你应该问的问题:

问:如果我不必使用特定机器并且它不必相同,除非在单个构建(或可能运行)的申请?

A:实现一个参数化的散列例程,可能使用各种原语,包括 SIMD 指令。在给定的硬件上,其中一些集合将起作用,您将希望使用编译时间#ifdefs 和动态 CPU 功能检测的某种组合来枚举该集合。 (例如,您不能在任何 ARM 处理器上使用 AVX2,这是在编译时确定的,并且您不能在较旧的 x86 上使用它,由 cpuinfo 指令确定。)采用有效的集合并在测试数据上计时感兴趣的机器。要么在系统/应用程序启动时动态执行此操作,要么尽可能多地测试案例,并根据某种嗅探算法硬编码在哪个系统上使用哪个例程。 (例如,Linux 内核这样做是为了确定最快的memcpy 例程等)

您需要哈希保持一致的情况将取决于应用程序。如果您需要完全在编译时进行选择,那么您需要制作一组编译器定义的预处理器宏。通常可能有多个实现产生相同的散列,但对不同的大小使用不同的硬件方法。

如果您正在定义一个新的哈希并希望它非常快,那么跳过 SIMD 可能不是一个好主意,尽管在某些应用程序中可能会在不使用 SIMD 的情况下使内存速度饱和,所以没关系。

如果所有这些听起来工作量太大,请使用size_t 作为累加器大小。或者使用std::atomic 告诉您类型是无锁的最大大小。请参阅:std::atomic_is_lock_freestd::atomic::is_lock_freestd::atomic::is_always_lock_free

【讨论】:

    【解决方案6】:

    我们必须假设“机器字长”的含义是:CPU 可以在单条指令中处理的最大数据块大小。 (有时称为数据总线宽度,虽然这是一种简化。)

    在各种 CPU:s 上,size_tuintptr_tptrdiff_t 可以是任何东西 - 这些与 地址总线宽度有关,而不是与 CPU 数据宽度有关。所以我们可以忘记这些类型,它们不会告诉我们任何事情。

    在所有主流 CPU:s 上,char 始终为 8 位,short 始终为 16 位,long long 始终为 64 位。所以剩下的唯一有趣的类型是intlong


    以下主流CPU确实存在:

    8 位

    int   = 16 bits   
    long  = 32 bits
    

    16 位

    int   = 16 bits   
    long  = 32 bits
    

    32 位

    int   = 32 bits   
    long  = 32 bits
    

    64 位

    int   = 32 bits   
    long  = 32 bits
    

    可能存在对上述内容的非常规变化,但从上述内容中通常无法说明如何区分 8 位与 16 位或 32 位与 64 位。

    对齐对我们也没有帮助,因为它可能适用于也可能不适用于各种 CPU:s。许多 CPU:s 可以很好地读取未对齐的单词,但代价是代码速度较慢。

    所以没有办法通过使用标准 C 来判断“机器字长”。


    然而,通过使用stdint.h 中的类型,尤其是uint_fast 类型,可以编写可以在8 到64 位之间运行的完全可移植的C。需要记住的一些事项是:

    • Implicit integer promotions 跨不同系统。 uint32_t 或更大的任何东西通常都是安全且便携的。
    • 整数常量(“文字”)的默认类型。这是最常见的(但并非总是)intint 在给定系统上的含义可能会有所不同。
    • 对齐和结构/联合填充。
    • 指针大小不一定与机器字大小相同。在许多 8、16 和 64 位计算机上尤其如此。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多