如何在C / C ++中检测机器字大小?

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

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

该标准似乎没有提供“机器字”数据类型。 因此,我不是在寻找100%可移植的方式,只适用于大多数常见情况(Intel x86 Pentium +,ARM,MIPS,PPC – 即基于寄存器的现代商品处理器)。

size_tuintptr_t听起来像是好的候选者(并且在实践中匹配我测试的所有寄存器大小)但当然是其他东西,因此不能保证总是这样做,如Is size_t中已经描述的那样。

上下文

假设我在一块连续数据上实现哈希循环。 结果哈希依赖于编译器是可以的,只有速度很重要。

示例: 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 

我想你想要的

sizeof(size_t) ,它应该是索引的大小。 即。 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 

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

 #include  #include  #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大小作为寄存器大小。

进一步结果

Windows 7,64位机器上的Cygwin下的gcc 4.9.3:与上面相同

Windows 7,64位计算机上的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,gcc 4.6.3在32位ARM和Linux上,gcc 5.3.1在32位Atom上:

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

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

  • character:一般来说它是可以与内存交换的最小元素 – 它现在几乎到处都是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 。 所以,只有使用适合该架构的编译器,上述内容才有意义……

我会给你一个你应该问的问题的正确答案:

问:如果我不必使用特定的机器,如何为特定机器选择最快的哈希例程,除了在应用程序的单个构建(或可能运行)之外,它不必相同?

答:实现参数化的散列程序,可能使用各种原语,包括SIMD指令。 在给定的硬件上,其中一些设置将起作用,您将需要使用编译时#ifdef和动态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

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

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

在所有主流CPU上: 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来说出“机器字大小”。


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

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

选择sizeof(int *)* CHAR_BIT以获得位的机器结构大小。

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