如何检测 C/C++ 中的机器字长?

How to detect machine word size in C/C++?

是否有或多或少可靠的方法(不一定完美)来检测目标体系结构机器字大小我正在为之编译?

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

标准似乎没有提供 "machine word" 数据类型。所以我 不是 寻找一种 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

由于 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 大小之间的最大差异,并使用int_fastXX_t 的大小作为寄存器大小。

进一步的结果

Windows7、64位机Cygwin下gcc 4.9.3:同上

Windows 7,Visual Studio 2013 (v 12.0) 在 64 位机器上:

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

我想你想要

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

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

  • 字符:一般来说,它是可以交换到内存或从内存交换的最小元素 - 现在几乎到处都是 8 位,但在一些较旧的体系结构上曾经是 6 位(80 年代初期的 CDC)
  • integer:一个整数寄存器(e.g.EAX 在 x86 上)。恕我直言,可接受的近似值是 sizeof(int)
  • address: 可以在架构上解决什么问题。恕我直言,可接受的近似值是 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 *) * CHAR_BIT 以获取机器架构大小(以位为单位)。

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

对于你应该问的问题,我会给你正确的答案:

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

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

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

如果您正在定义一个新的散列并希望它非常快,那么跳过 SIMD 可能不是一个好主意,尽管在某些应用程序中可能会在不使用 SIMD 的情况下使内存速度饱和,因此这无关紧要.

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

通过"machine word size"我们必须假设其含义是:CPU可以在一条指令中处理的最大数据块。 (有时称为数据总线宽度,尽管这是一种简化。)

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

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


以下主流 CPU:s 确实存在:

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 来告诉 "machine word size"。


然而,通过使用 stdint.h 中的类型,特别是 uint_fast 类型,可以编写完全可移植的 C,可以 运行 在 8 到 64 位之间的任何东西上。要记住的一些事情是:

  • 跨不同系统。 uint32_t 或更大的任何东西通常都是安全和便携的。
  • 整数常量的默认类型("literals")。这通常(但不总是)int,给定系统上的 int 可能会有所不同。
  • 对齐和 struct/union 填充。
  • 指针大小不一定与机器字大小相同。在许多 8、16 和 64 位计算机上尤其如此。