在 Windows 上开发C++程序,使用 long
数据类型发现很容易溢出,才想到 Windows 平台上 long
的字长和 int
一样是 4,而不是 Linux 上常见的 8。
数据类型和字长
根据 C++标准,有: sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
,同时又规定:
char
:至少8位(一个字节)short
:至少16位(两个字节)int
:至少16位(两个字节)long
:至少32位(4个字节)long long
:至少64位(8个字节)
int
的字长可以是 2,long
的字长可以是 4,因此 MSVC 下 long 和 int 大小一样没问题,只是比较少见。
展开来说,蒸熟类型的字长和编译器及操作系统采用的数据模型有关,已知的数据模型和字长关系有:
数据模型 | 解释 | short字长 | int字长 | long字长 | long long字长 |
LP32 | long和pointer是32位,即2/2/4模型 | 2 | 2 | 4 | 8 |
ILP32 | int、long和pointer是32位,即2/4/4模型 | 2 | 4 | 4 | 8 |
LP64 | long和pointer是64位,即4/8/8模型 | 2 | 4 | 8 | 8 |
LLP64 | long long和pointer是64位,即4/4/8模型 | 2 | 4 | 4 | 8 |
ILP64 | int、long和pointer是64位,即8/8/8模型 | 2 | 8 | 8 | 8 |
SILP64 | short、int、long和pointer是64位 | 8 | 8 | 8 | 8 |
char
都是8位,即一个字长(byte),目前没见到哪个神经病系统或者编译器使用其他字长
如今的桌面和移动主流处理器基本上支持64地址,因此现在32位模型较少使用(旧硬件、旧系统以及X32 ABI会用到);此外 SILP64 基本上也没见哪个编译器使用,实在是没必要。Windows 上的 MSVC(以及mingw环境下的GCC/clang) 使用的是 LLP64
模型,所以 int
和 long
都是4个字节。除了Windows,主流操作系统和编译器(绝大部分Linux/Unix、Macos,以及cygwin上的gcc/clang)都采用的是 LP64 模型,这也是为什么一般都会默认long
是8个字节,即64位。
除了 LP64 ,另一个在科学计算领域常见的模型是 ILP64,例如Intel MKL的某些库分 LP64 和 ILP64 版本。在大量数据处理的场景,int32
很容易溢出,因此矩阵/向量行列索引也需要用64位整数。在现代的处理器上,64位整数和32位整数运算的性能基本一致,但是会多占用一倍的内存。如果用到了第三方库(例如OpenBLAS),要让要求使用一致的数据模型,否则可能导致错误的结果。
定长整数类型
鉴于不同数据模型导致的基本类型字长不一样,对于跨平台的保证64位整数,long
是不可靠的。幸运的是,C++11引入了定长整数类型。对于定长整数有要求的场景,建议用以下数据类型:
std::int8/std::uint8
std::int16/std::uint16
std::int32/std::uint32
std::int64/std::uint64
另外一个常用类型时 size_t
,一般是 unsigned long long int
的别名,绝大多数情况下都能保证64位,不考虑符号的情况下够用了。但是对于 OpenMP for循环的场景,其要求索引是有符号整数,类似的少数场景下 size_t
无法使用。
对于范围更大的整数需求,例如 int128
,C/C++标准未提及,但是不少编译器已经内置支持。如果编译器不支持,则需要手动引入相应的开源代码。