uint8_t、uint_fast8_t和uint_least8_t之间的区别

Difference between uint8_t, uint_fast8_t and uint_least8_t

本文关键字:uint 之间 区别 least8 fast8 uint8      更新时间:2023-10-16

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

  • uint8_t表示它是 8 位无符号类型。
  • uint_fast8_t 意味着它是最快的无符号 int,至少有 8 个位。
  • uint_least8_t 意味着它是一个至少具有 8 位的无符号 int。

我了解uint8_t以及uint_fast8_t的内容(我不知道它是如何在寄存器级别实现的)。

1.你能解释一下"这是一个至少8位的unsigned int"是什么意思吗?

2.与uint8_t相比,uint_fast8_tuint_least8_t如何帮助提高效率/代码空间?

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,因为在我的虚构架构中,半寄存器变量比全寄存器变量慢。

uint8_t的意思是:给我一个正好 8 位的无符号 int。

uint_least8_t的意思是:给我至少 8 位的最小类型的无符号 int。针对内存消耗进行优化。

uint_fast8_t的意思是:给我一个至少 8 位的无符号 int。如果由于对齐考虑,如果这将使我的程序更快,请选择更大的类型。优化速度。

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

理论是这样的:

uint8_t需要正好是 8 位,但不需要存在。因此,您应该在依赖 8 位整数的模 256 赋值行为*的情况下使用它,并且您希望编译失败而不是晦涩架构上的错误行为。

uint_least8_t必须是可以存储至少 8 位的最小可用无符号整数类型。当您想要最大程度地减少大型数组等内容的内存使用时,您将使用它。

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

做法是"快速"和"最少"类型使用不多。

只有当你关心移植到具有 CHAR_BIT != 8 的模糊架构时,"最少"类型才真正有用,而大多数人都不知道。

"

快速"类型的问题在于"最快"很难确定。较小的类型可能意味着内存/缓存系统上的负载较少,但使用小于本机的类型可能需要额外的说明。此外,哪个最好可能会在架构版本之间更改,但实现者通常希望避免在这种情况下破坏 ABI。

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

总而言之,如果具有微小整数的紧凑代码的性能是您的目标,那么您应该在您关心的不同大小类型的平台上对您的代码进行基准测试,以查看哪种代码效果最好。

* 请注意,不幸的是,模 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;
}

如果y uint32_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

"快速"类型的预期目的是允许编译器用更快的类型替换无法有效处理的较小类型。 不幸的是,"快速"类型的语义规定得相当差,这反过来又留下了模糊的问题,即表达式将使用有符号数学还是无符号数学进行评估。

1.你能解释一下"这是一个至少 8 位的无符号 int"是什么意思吗?

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

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

2.与uint8_t相比,uint_fast8_t和uint_least8_t如何帮助提高效率/代码空间?

uint_fast8_t需要更快,因此如果您的要求是代码快速,则应使用它。 另一方面,uint_least8_t要求没有较小尺寸的候选者 - 因此,如果大小是问题,您将使用它。


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

"fast"整数类型定义为至少具有所需位数的最快可用整数(在本例中为 8)。

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

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

名思义,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位

我使用快速数据类型 (uint_fast8_t) 作为本地变量和函数参数,并在经常使用的数组和结构中使用普通数据类型 (uint8_t),内存占用比不必清除或签名扩展上位可以节省的几个周期更重要。效果很好,除了MISRA检查器。他们从快速类型中发疯。诀窍在于,快速类型是通过派生类型使用的,这些派生类型可以为 MISRA 构建和普通类型定义不同的类型。

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

这个线程中的一些猜测。"fast":编译器应将"快速"类型的变量放在 IRAM(本地处理器 RAM)中,与存储在 RAM 腹地的变量相比,访问和写入所需的周期更少。如果您需要对 var 执行尽可能快的操作,例如在中断服务例程 (ISR) 中,则使用"fast"。与声明函数具有IRAM_ATTR相同;这 == 更快的访问。"快速"或 IRAM 变量/函数的空间有限,因此仅在需要时使用,除非它们符合条件,否则永远不会持久。如果处理器 RAM 全部分配完毕,则大多数编译器会将"快速"变量移动到通用 RAM。