uint8_t 与无符号字符

Posted

技术标签:

【中文标题】uint8_t 与无符号字符【英文标题】:uint8_t vs unsigned char 【发布时间】:2010-12-16 02:24:28 【问题描述】:

在 C 中使用 uint8_tunsigned char 相比有什么优势?

我知道在几乎每个系统上uint8_t 只是unsigned char 的类型定义, 那为什么要使用它呢?

【问题讨论】:

【参考方案1】:

它记录了您的意图 - 您将存储小数字,而不是一个字符。

如果您使用其他类型定义,例如 uint16_tint32_t,它看起来会更好。

【讨论】:

显式使用 unsigned charsigned char 也记录了意图,因为朴素的 char 表明您正在使用字符。 我认为朴素的unsignedunsigned int 的定义? @endolith,对字符串使用 uint8_t 不一定是错的,但肯定很奇怪。 @endolith,我想我可以用 UTF8 文本为 uint8_t 提供一个案例。实际上,char 似乎暗示了一个字符,而在 UTF8 字符串的上下文中,它可能只是多字节字符的一个字节。使用 uint8_t 可以清楚地表明,不应期望每个位置都有一个字符——换句话说,字符串/数组的每个元素都是一个任意整数,不应对其进行任何语义假设。当然,所有 C 程序员都知道这一点,但它可能会促使初学者提出正确的问题。 我不得不说,unsigned char 并不是真正用来存储字符的,所以“意图”问题没有实际意义。【参考方案2】:

正如你所说,“几乎每个系统”。

char 可能是不太可能改变的一种,但一旦你开始使用uint16_t 和朋友,使用uint8_t 会更好地融合,甚至可能成为编码标准的一部分。

【讨论】:

【参考方案3】:

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

【讨论】:

uint8_t always 匹配unsigned char 的范围和大小以及unsigned char 为8 位时的填充(无)。当unsigned char不是8位时,uint8_t不存在。 @chux,您是否参考了标准中的确切位置?如果unsigned char 是8 位的,那么uint8_t 是否保证是其中的typedef 而不是扩展无符号整数类型typedef @hsivonen “它在标准中的确切位置是什么?” --> 否 - 还需要查看 7.20.1.1。很容易推断,unsigned char/signed char/char 是最小的类型——不小于 8 位。 unsigned char 没有填充。对于uint8_t,它必须是8 位,没有填充,因为实现提供的整数类型而存在:匹配unsigned char 的最低要求。至于“...保证是 typedef ...”看起来是个不错的问题。【参考方案4】:

为了迂腐,有些系统可能没有 8 位类型。根据Wikipedia:

当且仅当它具有满足要求的任何类型时,需要一个实现来定义 N = 8、16、32 或 64 的精确宽度整数类型。不需要为任何其他 N 定义它们,即使它支持适当的类型。

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

除了那个(次要)问题,@Mark Ransom's answer 在我看来是最好的。使用最清楚地表明您将数据用于什么目的的那个。

另外,我假设您的意思是 uint8_tstdint.h 标头中提供的 C99 标准 typedef)而不是 uint_8(不是任何标准的一部分)。

【讨论】:

@caf,纯粹出于好奇——你能链接到一些描述吗?我知道它们的存在是因为有人在 comp.lang.c++.moderated 讨论中提到了一个(并链接到它的开发人员文档),讨论 C/C++ 类型保证是否太弱,但我再也找不到那个线程了,而且它总是很方便在任何类似的讨论中引用它:) “某些系统可能将 char 类型定义为 16 位,在这种情况下可能不会有任何类型的 8 位类型。” - 尽管我提出了一些不正确的反对意见,Pavel 在他的回答中已经证明,如果 char 是 16 位,那么即使编译器确实提供了 8 位类型,它不得调用它uint8_t(或 typedef 到那个)。这是因为 8bit 类型在存储表示中会有未使用的位,uint8_t 一定没有。 SHARC 架构有 32 位字。详情请见en.wikipedia.org/wiki/…。 TI 的 C5000 DSP(在 OMAP1 和 OMAP2 中)是 16 位的。我认为对于 OMAP3,他们选择了 C6000 系列,具有 8 位字符。 深入研究 N3242 -“工作草案,C++ 编程语言标准”,第 18.4.1 节 概要说 - typedef unsigned integer type uint8_t; // optional 因此,本质上,不需要符合 C++ 标准的库完全定义 uint8_t(参见注释 //可选)【参考方案5】:

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

它可以用来更好地记录您的代码,因为很明显您需要 8 位字节,而无需其他任何东西。但在实践中,它几乎在任何地方都是一个合理的期望(在某些 DSP 平台上它不是真的,但是你的代码在那里运行的机会很小,你也可以在你的程序顶部使用静态断言出错这样的平台)。

【讨论】:

为了记录,您可以在任何平台上制作一个 8 位类型:typedef struct unsigned i :8; uint8_t; 但您必须将其用作uint8_t x; x.i = ...,这样会比较麻烦。 @Skizz - 不,标准要求 unsigned char 能够保存 0 到 255 之间的值。如果你能在 4 位中做到这一点,我的帽子向你致敬。 “会更麻烦一些” - 从某种意义上说,您必须步行(游泳、乘飞机等)一直走到编译器编写者所在的位置,这很麻烦,拍他们的后脑勺,让他们在实现中添加uint8_t。我想知道,具有 16 位字符的 DSP 的编译器通常会实现uint8_t,还是不实现? 顺便说一下,再想一想,这可能是说“我真的需要 8 位”-#include <stdint.h> 并使用uint8_t 最直接的方式。如果平台有,它会给你。如果平台没有,你的程序将无法编译,原因就很清楚了。 Still no cigar, sorry: "对于 unsigned char 以外的无符号整数类型,对象表示的位应分为两组:值位和填充位...如果有 N 个值位,每个位应表示 1 和 2^(N-1) 之间的 2 的不同幂,因此该类型的对象应能够使用纯二进制表示表示从 0 到 2^(N-1) 的值。 .. typedef 名称 intN_t 指定宽度为 N、无填充位和二进制补码表示的有符号整数类型。”【参考方案6】:

重点是编写独立于实现的代码。 unsigned char 不保证是 8 位类型。 uint8_t 是(如果有的话)。

【讨论】:

...如果它存在于系统上,但这将非常罕见。 +1 好吧,如果你真的因为 uint8_t 不存在而无法在系统上编译代码时遇到问题,你可以使用 find 和 sed 自动将所有出现的 uint8_t 更改为 unsigned char 或更有用的东西你。 @bazz - 如果您假设它是 8 位类型,则不能 - 例如,解压缩远程系统以字节方式打包的数据。隐含的假设是 uint8_t 不存在的原因是在 char 超过 8 位的处理器上。 抛出断言 assert(sizeof(unsigned char) == 8); @bazz 不正确的断言恐怕。 sizeof(unsigned char) 将返回 1 1 个字节。但如果系统 char 和 int 的大小相同,例如 16 位,则 sizeof(int) 也将返回 1【参考方案7】:

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

【讨论】:

当我问这个问题时,我定义了一个简单的串行通信协议。【参考方案8】:

根据我的经验,我们希望在两个地方使用 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

Is gcc's __attribute__((packed)) / #pragma pack unsafe?

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

【讨论】:

以上是关于uint8_t 与无符号字符的主要内容,如果未能解决你的问题,请参考以下文章

正确解释有符号与无符号

Verilog -- 有符号与无符号运算

有符号整数与无符号整数

C之有符号与无符号

第2课 有符号与无符号

关于:有符号与无符号整数的大小比较