C/C++中的固定长度数据类型

75

我听说像int这类的数据类型在不同平台上大小可能会有所不同。

我的第一个问题是:能否举一些例子,当程序假设一个int是4个字节时,但在另一个平台上它只有2个字节,会出现什么问题?

我还有一个相关的问题。我知道人们用一些typedefs来解决这个问题,比如你有像u8u16u32这样的变量——无论平台如何,它们都保证是8位、16位、32位——我的问题是,通常是如何实现这个的?(我不是在引用stdint库中的类型——我想了解手动方式,如何强制某些类型始终为32位,而不考虑平台?)


3
过度写入内存可能存在问题。如果你假设一个整数是4个字节,而在另一个平台上只有2个字节,根据内存的布局方式,在整数后面的下一个2个字节可能会被覆盖。 - Austin Brunkhorst
9
建议阅读(虽然有点老但仍然非常有用的)C语言FAQ,链接如下: http://www.faqs.org/faqs/C-faq/abridged/ 以及 http://www.faqs.org/faqs/C-faq/faq/ (完整版,如果可以请阅读完整版,其中包含更多信息)。这些文档涵盖了许多常见的问题和误解,例如空指针的内部表示等。强烈建议阅读关于NULL、指针/数组的章节,其他内容也非常好,可以帮助你开阔眼界。 - Olivier Dulac
1
请注意,字节顺序也可能因平台而异。(对于问题加1分 - 提问总比假设“sizeof(void *)肯定是4”要好。) - Maciej Piechotka
@MaciejPiechotka:同意。发帖让读者了解潜在的陷阱和解决方案是很好的!没有坏问题[好吧,如果它们提供足够的上下文],只有糟糕的答案^^ - Olivier Dulac
11个回答

41
我知道人们通过一些typedef解决这个问题,比如你有变量像u8、u16、u32——无论平台如何,它们都保证是8位、16位、32位。

有些平台没有某些大小的类型(比如TI的28xxx,其中char的大小为16位)。在这种情况下,就不可能有一个8位的类型(除非你真的想要它,但这可能会影响性能)。

通常使用typedefs来实现。C99(和C++11)在这个头文件中有这些typedefs。所以,只需要使用它们即可。

最好的例子是在不同类型大小的系统之间进行通信。从一个平台发送int数组到另一个平台,其中sizeof(int)在两个平台上不同,必须格外小心。

此外,在32位平台上将整数数组保存在二进制文件中,并在64位平台上重新解释它。

14
在32位平台上将整数数组保存到二进制文件中,并在64位平台上重新解释它。 - legends2k

22
在早期C标准的迭代中,通常需要创建自己的typedef语句来确保获得指定位数的类型(例如16位),基于传递给编译器的#define字符串。
gcc -DINT16_IS_LONG ...

现今(在C99及以上版本中),有特定的类型,如uint16_t,它是一个精确的16位无符号整数。

只要包含stdint.h头文件,就可以获得精确的位宽类型、至少该宽度的类型、给定最小宽度的最快类型等,详见C99 7.18 整型 <stdint.h>。如果实现具有兼容的类型,则必须提供这些类型。

inttypes.h也非常有用,它为这些新类型的格式转换(printfscanf格式字符串)添加了一些其他方便的功能。


1
子问题:如果平台不支持16位整数类型,cstdint中是否未定义uint16_t等类型?或者标准保证该类型始终存在(并在内部执行操作以确保其正常工作)? - Martin York
5
如果实现有兼容类型,则C标准只要求提供相应的typedef名称。例如,如果您在12位DSP上运行,则不必提供16位uint16_t。它可以提供,但不是强制性的:7.18.1.1/3:这些类型是可选的。但是,如果实现提供了8、16、32或64位宽度的整数类型,没有填充位,并且(对于有符号类型)具有二进制补码表示,则应定义相应的typedef名称。 - paxdiablo
4
如果您使用uint16_t且平台不支持它,那么在移植过程中我们可以预期会出现编译错误。 - Martin York
1
@Loki,是的,编译器不会知道类型。 - paxdiablo

16

对于第一个问题:整数溢出

对于第二个问题:例如,在 int 是 4 字节的平台上,要 typedef 一个无符号的 32 位整数,使用以下代码:

 typedef unsigned int u32;

在一个 int 占用 2 字节而 long 占用 4 字节的平台:

typedef unsigned long u32;

通过这种方式,您只需要修改一个头文件就可以使类型跨平台。

如果有一些特定于平台的宏定义,这可以在不手动修改的情况下实现:

#if defined(PLAT1)
typedef unsigned int u32;
#elif defined(PLAT2)
typedef unsigned long u32;
#endif
如果支持C99的stdint.h,则优先使用。

没关系,这种时候很正常...休息一下吧! - alk
这里所说的平台是指什么?是像x86、x86_64、AMD等硬件平台,还是类似于Solaris、AIX、HP-UX、Linux、macOS、BSD和IBM z/OS等操作系统平台? - Darshan L

8
首先:永远不要编写依赖于像shortintunsigned int等类型宽度的程序......
基本上,"如果标准没有保证宽度,就永远不要依赖于宽度"。
如果你想真正做到跨平台,并且例如将值33000存储为有符号整数,你不能仅仅假设int可以容纳它。一个int至少具有范围-3276732767或者-3276832767(取决于补码/反码)。即使它通常是32位,因此可以容纳33000,但这显然是不够的。对于这个值,你需要一个>16bit类型,因此你只需选择int32_tint64_t。如果这种类型不存在,编译器将会告诉你错误,但它不会是一个静默的错误。
其次:C++11提供了一个用于固定宽度整数类型的标准头文件。这些类型都不能保证在你的平台上存在,但是当它们存在时,它们保证具有精确的宽度。请参见cppreference.com上的这篇文章。这些类型的名称采用int[n]_tuint[n]_t的格式,其中n8163264。你需要包括头文件<cstdint>。当然,C头文件是<stdint.h>

2
我不是指stdint库中的类型 - 我好奇手动地,如何强制某种类型始终保持32位,而不考虑平台? - legends2k
2
@legends2k 正确的固定宽度整数类型使用方式是使用标准库。 - stefan
4
同意,但这是当你编写代码时,而不是当你试图学习首先如何编写这些头文件时。 - legends2k
7
首先,永远不要编写依赖于数据类型宽度的程序。这句话的意思是我们不应该依赖uint32_t的宽度为32位吗?抽象化非常好用,但最终会到达需要做出某些假设才能实际完成工作的时候。 - Thomas
6
“永远不要编写依赖于类型宽度的程序”是什么意思?类型的宽度直接影响可能的值范围,在选择使用何种类型时非常重要,特别是对于许多人使用C/C++进行的编程任务。如果您正在编写文件系统或需要在受限内存中存储大量值的任何内容,则需要做出这些决策。之所以不将字符串存储为无符号长长整型数组,是有原因的。 - tfinniga
显示剩余6条评论

6
通常情况下,当您达到最大数量或进行序列化时,会出现此问题。较少见的情况是某人做出明确的大小假设。
在第一种情况下:
int x = 32000;
int y = 32000;
int z = x+y;        // can cause overflow for 2 bytes, but not 4

在第二种情况下,
struct header {
int magic;
int w;
int h;
};

接下来会执行fwrite:

header h;
// fill in h
fwrite(&h, sizeof(h), 1, fp);

// this is all fine and good until one freads from an architecture with a different int size

在第三种情况下:
int* x = new int[100];
char* buff = (char*)x;


// now try to change the 3rd element of x via buff assuming int size of 2
*((int*)(buff+2*2)) = 100;

// (of course, it's easy to fix this with sizeof(int))

如果你使用的是相对较新的编译器,我建议使用uint8_t、int8_t等类型来确保类型大小。

在旧版本的编译器中,typedef通常是基于平台定义的。例如,可能会这样做:

 #ifdef _WIN32
      typedef unsigned char uint8_t;
      typedef unsigned short uint16_t;
      // and so on...
 #endif

这样,每个平台都会有一个头文件,定义该平台的特定内容。


2
+1 如果你是第一个提到结构体的人。你还应该知道当你将结构体发送到网络上时会发生什么。 - James Anderson

5
如果你想让你的(现代)C++程序在给定类型不是你所期望的宽度时编译失败,请添加一个static_assert。我会在对类型宽度的假设被提出的地方添加这个。
static_assert(sizeof(int) == 4, "Expected int to be four chars wide but it was not.");

大多数常用平台上的chars大小为8位,但并非所有平台都是这样工作的。


3
sizeof 实际上返回以“字节”为单位的大小,而不是字符。因此,如果您想检查以“位”为单位的大小,应该执行 sizeof(int) * CHAR_BIT == 32 - user694733
static_assert仅在最新标准中可用。但是uint_32t和类似类型在之前就已经可用了。 - Sam
@user694733 不是的。按定义,字符大小等于字节大小。sizeof(char)==1 - 总是这样。 - Konrad Rudolph
@sammy 不对,uint32_t等类型是在static_assert同时添加的。 - Konrad Rudolph
@user694733 这是一个很好的观点。实际上,我只是回复了你第一条评论的第一部分。 - Konrad Rudolph
显示剩余3条评论

3

好的,第一个例子 - 就像这样:

int a = 45000; // both a and b 
int b = 40000; // does not fit in 2 bytes.
int c = a + b; // overflows on 16bits, but not on 32bits

如果您查看cstdint头文件,您会发现所有固定大小类型(int8_t,uint8_t等)的定义 - 不同架构之间唯一的区别就是这个头文件。所以,在一个架构上,int16_t可以是:

 typedef int int16_t;

还有另一个:

 typedef short int16_t;

此外,还有其他类型可能会很有用,例如:int_least16_t

2
  1. 如果一个类型比你想象的要小,那么它可能无法存储你需要存储在其中的值。
  2. 要创建固定大小的类型,你需要阅读支持的平台的文档,然后基于特定平台的#ifdef定义typedef

2
如果程序假设int类型是4个字节,但在另一个平台上只有2个字节,会出现什么问题?举个例子吧。假设你的程序需要读取100,000个输入,并使用unsigned int来计数,假定它的大小为32位(32位unsigned int可以计数到4,294,967,295)。如果在一个只有16位整数的平台(或编译器)上编译代码(16位unsigned int只能计数到65,535),那么值将会由于容量的限制而超过65535并产生错误的计数结果。

1

编译器需要遵守标准。当您包含 <cstdint> 或者 <stdint.h> 时,编译器应该提供符合标准的类型。

编译器知道正在为什么平台编译代码,然后可以生成一些内部宏或魔法来构建适当的类型。例如,在 32 位机器上的编译器会生成 __32BIT__ 宏,并且先前在 stdint 头文件中有这些行:

#ifdef __32BIT__
typedef __int32_internal__ int32_t;
typedef __int64_internal__ int64_t;
...
#endif

并且您可以使用它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接