uint8_t、uint_fast8_t 和 uint_least8_t 之间的区别

Difference between uint8_t, uint_fast8_t and uint_least8_t

C99 标准引入了以下数据类型。可以找到 here AVR stdint 库的文档。

我了解uint8_t什么是uint_fast8_t(我不知道它是如何在寄存器级实现的)。

1.Can你解释一下"it's an unsigned int with at least 8 bits"是什么意思?

2.How uint_fast8_tuint_least8_t 相较于 uint8_t?

帮助增加 efficiency/code space

uint_least8_t 是最小的类型,至少有 8 位。 uint_fast8_t 是最快的类型,至少有 8 位。

你可以通过想象奇异的建筑来看到差异。想象一个 20 位架构。它的 unsigned int 有 20 位(一个寄存器),它的 unsigned char 有 10 位。所以 sizeof(int) == 2,但是使用 char 类型需要额外的指令来将寄存器减半。那么:

  • uint8_t: 未定义(无 8 位类型)。
  • uint_least8_t:是unsigned char,最小的至少8位的类型
  • uint_fast8_t:是unsigned int,因为在我想象的架构中,half-register变量比full-register变量慢。

uint8_t 意思是:给我一个刚好 8 位的 unsigned int。

uint_least8_t 的意思是:给我至少有 8 位的最小类型的 unsigned int。优化内存消耗。

uint_fast8_t的意思是:给我一个至少8位的unsigned int。出于对齐方面的考虑,如果它能让我的程序更快,请选择一个更大的类型。优化速度。

此外,与普通 int 类型不同,上述 stdint.h 类型的签名版本保证为 2 的补码格式。

"fast" 整数类型被定义为可用的最快整数,至少具有所需的位数(在您的情况下为 8)。

一个平台可以把uint_fast8_t定义成uint8_t那么速度上绝对没有区别。

原因是有些平台在不使用其本机字长时速度较慢。

1.Can you explain what is the meaning of "it's an unsigned int with at least 8 bits"?

这应该是显而易见的。这意味着它是一个无符号整数类型,并且它的宽度至少为 8 位。实际上这意味着它至少可以容纳 0 到 255 之间的数字,绝对不能容纳负数,但它可能可以容纳大于 255 的数字。

显然,如果您打算存储 0 到 255 范围之外的任何数字(并且您希望它是可移植的),则不应使用这些类型中的任何一种。

2.How uint_fast8_t and uint_least8_t help increase efficiency/code space compared to the uint8_t?

uint_fast8_t 需要更快,所以如果您的要求是代码更快,您应该使用它。 uint_least8_t 另一方面要求没有较小尺寸的候选者 - 所以如果尺寸是问题,你会使用它。


当然,当您绝对需要它正好是 8 位时,您只使用 uint8_t。使用 uint8_t 可能会使代码 non-portable 因为 uint8_t 不需要存在(因为在某些平台上不存在这种小整数类型)。

理论是这样的:

uint8_t 需要恰好是 8 位,但不需要存在。因此,您应该在依赖 8 位整数的 modulo-256 赋值行为* 以及希望编译失败而不是在晦涩的体系结构上出现错误行为的地方使用它。

uint_least8_t 必须是可以存储至少 8 位的最小可用无符号整数类型。当你想最小化大数组等东西的内存使用时,你会使用它。

uint_fast8_t应该是"fastest"无符号类型,至少可以存储8位;但是,对于任何给定处理器上的任何给定操作,它实际上并不能保证是最快的。您将在处理对值执行大量操作的代码中使用它。

实践是"fast"和"least"类型用得不多

"least" 类型只有在您关心使用 CHAR_BIT != 8 来掩盖架构的可移植性时才真正有用,而大多数人并不关心。

"fast" 类型的问题是 "fastest" 很难确定。较小的类型可能意味着 memory/cache 系统上的负载较小,但使用小于本机的类型可能需要额外的指令。此外,最好的架构版本可能会有所不同,但实施者通常希望避免在这种情况下破坏 ABI。

从一些流行的实现来看,uint_fastn_t 的定义似乎相当随意。 glibc 似乎将它们定义为至少是所讨论系统的 "native word size",而没有考虑到许多现代处理器(尤其是 64 位处理器)对小于其本机字的项目的快速操作具有特定支持的事实尺寸。 IOS 显然将它们定义为等同于 fixed-size 类型。其他平台可能有所不同。

总而言之,如果使用小整数的紧凑代码的性能是您的目标,那么您应该 bench-marking 您的 代码在您关心的平台上具有不同大小的类型看看哪个效果最好。

* 请注意,不幸的是,模 256 赋值行为并不总是意味着模 256 算术,这要归功于 C 的整数提升错误特征。

一些处理器无法像处理大型数据类型那样高效地处理较小的数据类型。例如,给定:

uint32_t foo(uint32_t x, uint8_t y)
{
  x+=y;
  y+=2;
  x+=y;
  y+=4;
  x+=y;
  y+=6;
  x+=y;
  return x;
}

如果 yuint32_t ARM Cortex-M3 的编译器可以简单地生成

add r0,r0,r1,asl #2   ; x+=(y<<2)
add r0,r0,#12         ; x+=12
bx  lr                ; return x

但由于 yuint8_t,编译器将不得不生成:

add r0,r0,r1          ; x+=y
add r1,r1,#2          ; Compute y+2
and r1,r1,#255        ; y=(y+2) & 255
add r0,r0,r1          ; x+=y
add r1,r1,#4          ; Compute y+4
and r1,r1,#255        ; y=(y+4) & 255
add r0,r0,r1          ; x+=y
add r1,r1,#6          ; Compute y+6
and r1,r1,#255        ; y=(y+6) & 255
add r0,r0,r1          ; x+=y
bx  lr                ; return x

"fast" 类型的预期目的是允许编译器用更快的类型替换无法有效处理的较小类型。不幸的是,"fast" 类型的语义没有明确规定,这反过来又留下了一个模糊的问题,即是否使用有符号或无符号数学来计算表达式。

我将快速数据类型 (uint_fast8_t) 用于局部变量和函数参数,并在经常使用的数组和结构中使用普通数据类型 (uint8_t),内存占用是比不必清除或符号扩展高位可以节省的几个周期更重要。 效果很好,除了 MISRA 跳棋。他们对快速类型发疯。诀窍是快速类型是通过派生类型使用的,派生类型可以为 MISRA 构建和普通构建定义不同的类型。

我认为这些类型非常适合创建可移植代码,它们在低端微控制器和大型应用程序处理器上都很高效。改进可能不是很大,或者对于好的编译器来说完全可以忽略不计,但总比没有好。

本帖中的一些猜测。 “快速”:编译器应该将“快速”类型的变量放在 IRAM(本地处理器 RAM)中,这比存储在 RAM 腹地的变量需要更少的周期来访问和写入。如果您需要对 var 采取尽可能快的操作,例如在中断服务程序 (ISR) 中,则使用“快速”。与声明函数具有 IRAM_ATTR 相同;这 == 更快的访问。 “快速”或 IRAM vars/functions 的限制 space,因此仅在需要时使用,除非符合条件,否则永远不要坚持。如果处理器 RAM 已全部分配,大多数编译器会将“快速”变量移动到通用 RAM。

顾名思义,uint_least8_t是最小的至少8位的类型,uint_fast8_t是最快的至少8位的类型。 uint8_t 恰好有 8 位,但不能保证在所有平台上都存在,尽管这种情况极为罕见。

在大多数情况下,uint_least8_t = uint_fast8_t = uint8_t = unsigned char。我见过的唯一例外是德州仪器的C2000 DSP,它是32位,但它的最小数据宽度是16位。它没有uint8_t,只能用uint_least8_tuint_fast8_t,它们定义为unsigned int,也就是16位.