uint8_t相对于unsigned char的优势是什么?

290
在C语言中,使用`uint8_t`相比于`unsigned char`有什么优势?
我知道在几乎所有的系统中,`uint8_t`只是`unsigned char`的一个typedef,那么为什么要使用它呢?
8个回答

282

它记录了你的意图 - 你将要存储小数值,而不是字符。

如果你使用其他typedefs,例如uint16_tint32_t,也会更好看。


12
显式地使用unsigned charsigned char也可以记录意图,因为未修饰的char表明您正在处理字符。 - caf
10
按照定义,未加修饰符的 unsigned 就是指 unsigned int 吗? - Mark Ransom
7
@endolith,使用uint8_t表示字符串可能不是错的,但肯定很奇怪。 - Mark Ransom
7
@endolith,我认为我可以为UTF8文本使用uint8_t来提出论据。实际上,char似乎暗示一个字符,而在UTF8字符串的上下文中,它可能只是多字节字符的一个字节。使用uint8_t可以清楚地表明,在每个位置都不应该期望一个字符——换句话说,字符串/数组的每个元素都是一个任意的整数,不能对其进行语义假设。当然,所有C程序员都知道这一点,但这可能会促使初学者提出正确的问题。 - tne
3
我必须说,“unsigned char”并不是用来存储字符的,因此“意图”问题是无关紧要的。 - user541686
显示剩余5条评论

91

仅仅是挑剔一下,有些系统可能没有8位类型。根据维基百科

如果实现符合要求,则需要为N = 8、16、32或64定义精确宽度整数类型;如果存在任何满足要求的类型,则不需要为任何其他N定义它们,即使它支持适当的类型也不需要。

因此,不能保证存在uint8_t,尽管在所有8位=1字节的平台上都会存在。某些嵌入式平台可能会有所不同,但这已经非常罕见了。某些系统可能将char类型定义为16位,在这种情况下,可能不会有任何8位类型。

除了这个(小)问题外,我认为@Mark Ransom的回答是最好的。使用最能清楚地显示您正在使用数据的方法。

另外,我假设您指的是uint8_t(C99中提供的标准typedef,位于stdint.h标头),而不是uint_8(不属于任何标准)。


3
@caf,出于好奇,你能否提供一些描述的链接?我知道它们存在,因为有人在comp.lang.c++.moderated讨论中提到过一个(并链接到开发者文档),讨论了C/C++类型保证是否太弱,但我现在找不到那个线程了,在任何类似的讨论中引用它总是很方便的 :) - Pavel Minaev
3
如果有些系统将char类型定义为16位,则可能不会有任何8位类型。尽管我曾经提出了一些错误的反对意见,但Pavel在他的回答中已经证明,如果char是16位,则即使编译器提供了8位类型,也不能称它为uint8_t(或将其typedef为该类型)。这是因为8位类型在存储表示中会有未使用的位,而uint8_t不能有这种情况。 - Steve Jessop
3
SHARC 架构使用 32 位字长。有关详细信息,请参阅 http://en.wikipedia.org/wiki/Super_Harvard_Architecture_Single-Chip_Computer。 - BCran
4
深入研究《C++编程语言标准》草案N3242中的第18.4.1节<cstdint>摘要,其中写道: `typedef unsigned integer type uint8_t; // optional`因此,本质上,符合C++标准的库根本不需要定义uint8_t(请参见注释//optional)。 - nightlytrails
2
在最小数据类型大于8位的情况下(例如Ti的C2000系列它们是16位),我认为可以使用uint_least8_t来正确表示意图和事实,即该类型实际上可能不是8位。 - Toby
显示剩余3条评论

65

写代码的整个目的就是编写与实现无关的代码。unsigned char 不保证是8位类型。uint8_t 是(如果可用)。


5
如果这个东西存在于系统中,但这种情况非常罕见。+1 - Chris Lutz
2
如果你的代码在某个系统上无法编译,因为 uint8_t 不存在,那么你可以使用 find 和 sed 命令自动将所有 uint8_t 的出现替换为 unsigned char 或其他对你更有用的类型。 - bazz
3
@bazz - 如果你假设它是一个8位类型,那么你就不能这样做 - 例如,解包由远程系统以字节方式打包的数据。隐含的假设是uint8_t不存在的原因是在一个char超过8位的处理器上。 - Chris Stratton
6
抱歉,@bazz的说法是错误的。sizeof(unsigned char)将返回1字节大小。但如果系统中的字符和整数大小相同,例如16位,则sizeof(int)也将返回1。 - Toby
1
更好的是,你宁愿让编译器明确地报告出你期望的无符号8位整数不存在的错误,也不要让你的代码在后面崩溃和死机...(当然,如果你的代码不依赖于这些字符是8位的话,那么当然可以自由地称它们为字符!) - JamesTheAwesomeDude
显示剩余6条评论

10

正如您所说,"几乎每个系统"。

char可能是最不可能更改的类型之一,但一旦开始使用uint16_t及其相关类型,则使用uint8_t可以更好地融合,并且甚至可能成为编码标准的一部分。


7
根据我的经验,有两个场景需要使用uint8_t表示8位(以及uint16_t等),并且可以使用小于8位的字段。这两个场景都与空间有关,我们通常需要在调试时查看数据的原始转储,并能够快速确定它所代表的含义。
第一个场景是在射频协议中,特别是在窄带系统中。在这种环境下,我们可能需要将尽可能多的信息打包到单个消息中。
第二个场景是在闪存存储中,我们可能具有非常有限的空间(例如在嵌入式系统中)。在这两种情况下,我们可以使用紧凑的数据结构,编译器将为我们处理打包和解包。
#pragma pack(1)
typedef struct {
  uint8_t    flag1:1;
  uint8_t    flag2:1;
  padding1   reserved:6;  /* not necessary but makes this struct more readable */
  uint32_t   sequence_no;
  uint8_t    data[8];
  uint32_t   crc32;
} s_mypacket __attribute__((packed));
#pragma pack()

你使用的方法取决于你的编译器。你可能还需要使用相同的头文件支持几个不同的编译器。这种情况在嵌入式系统中经常发生,其中设备和服务器可以完全不同 - 例如,你可能有一个与x86 Linux服务器通信的ARM设备。
使用紧凑结构有一些注意事项。最大的问题是必须避免解引用成员的地址。在具有多字节对齐字的系统上,这可能会导致未对齐异常 - 和核心转储。
一些人也会担心性能,并争论使用这些紧凑结构会减慢系统速度。事实上,编译器在幕后添加了访问未对齐数据成员的代码。你可以通过查看IDE中的汇编代码来看到这一点。
但是,由于紧凑结构最有用于通信和数据存储,因此当在内存中处理数据时,数据可以被提取为非紧凑表示形式。通常我们不需要在内存中处理整个数据包。
以下是一些相关讨论: pragma pack(1) nor __attribute__ ((aligned (1))) works

gcc的__attribute__((packed)) / #pragma pack是否安全?

http://solidsmoke.blogspot.ca/2010/07/woes-of-structure-packing-pragma-pack.html


7

很少有例外。从可移植性的角度来看,char 不能小于8位,而且没有比 char 更小的类型,因此如果给定的 C 实现具有无符号8位整数类型,则它将是 char。或者可能根本没有这种类型,此时任何 typedef 技巧都没有用。

使用它可以更好地记录代码,因为它清楚地表明您在那里需要8位字节,而不需要其他任何东西。但实际上,在几乎任何情况下,这已经是一个合理的期望(有一些 DSP 平台上并非如此,但您的代码在那里运行的机会很小,您可以在程序顶部使用静态断言报告错误)。


9
不,标准要求“unsigned char”能够容纳0到255之间的值。如果您可以用4位实现这一点,那我向您致敬。 - Chris Lutz
2
“it'd be a bit more cumbersome” - 意思是你需要一路走(游泳,乘飞机等)到编译器作者所在的地方,拍打他们的脑袋,让他们将 uint8_t 添加到实现中。我想知道,对于具有16位字符的DSP的编译器通常是否会实现 uint8_t - Steve Jessop
6
顺便说一句,经过重新考虑,也许最直接的方法是说“我真的需要8位” - #include <stdint.h>,然后使用uint8_t。如果平台有它,它会给你。如果平台没有它,你的程序将无法编译,并且原因将是清晰明了的。 - Pavel Minaev
2
还差一点,抱歉:“对于除了unsigned char之外的无符号整数类型,对象表示的位应分为两组:值位和填充位...如果有N个值位,则每个位应表示1到2^(N-1)之间不同的2的幂,以便该类型的对象能够使用纯二进制表示表示从0到2^(N-1)的值...typedef名称intN_t指定具有宽度N、__没有填充位__和二进制补码表示的有符号整数类型。” - Pavel Minaev
1
如果你只需要算术模运算,无符号位域就可以胜任(虽然有些不方便)。当你需要一个没有填充的八位字节数组时,你就会遇到麻烦。故事的寓意是不要为DSP编写代码,而应坚持使用适当、诚实的8位字符架构 :) - Pavel Minaev
显示剩余12条评论

4

在编写网络分析器时,这非常重要。数据包头由协议规范定义,而不是特定平台的C编译器工作方式决定。


当我提出这个问题时,我正在定义一个简单的串行通信协议。 - Frames Catherine White

2

在我遇到的几乎所有系统中,uint8_t == unsigned char,但这并不被C标准保证。如果您正在编写可移植的代码,并且内存大小很重要,请使用uint8_t。否则请使用unsigned char。


4
uint8_t一直匹配unsigned char的范围和大小,当unsigned char是8位时没有填充。当unsigned char不是8位时,uint8_t不存在。 - chux - Reinstate Monica
@chux,你有标准中确切说明这一点的参考资料吗?如果unsigned char是8位,那么uint8_t是否保证是其typedef,而不是扩展无符号整数类型的typedef - hsivonen
@hsivonen "精确的标准位置在哪里?" --> 不是 - 但看看7.20.1.1。很容易推断出unsigned char/signed char/char是最小的类型 - 不小于8位。 unsigned char没有填充。对于uint8_t而言,它必须是8位,没有填充,并存在于实现提供的整数类型中:与unsigned char的最小要求相匹配。至于“...保证是typedef...” 看起来是一个好问题要发布。 - chux - Reinstate Monica

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