uint8_t、uint_fast8_t 和 uint_least8_t 之间的区别

Posted

技术标签:

【中文标题】uint8_t、uint_fast8_t 和 uint_least8_t 之间的区别【英文标题】:Difference between uint8_t, uint_fast8_t and uint_least8_t 【发布时间】:2016-05-05 10:45:22 【问题描述】:

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

uint8_t 表示它是一个 8 位无符号类型。 uint_fast8_t 表示它是最快的无符号整数,至少为 8 位。 uint_least8_t 表示它是一个至少 8 位的无符号整数。

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

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

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

【问题讨论】:

对于您的第一个问题,我可以想象,uint8_t 保证为 8 位,uint_fast8_t 保证为 >= 8 位,很像 unsigned char 一个考虑因素是uint8_t 在没有本机 8 位类型的系统上不存在。另外两个会在那里。 您得到的答案涉及“晦涩”和“异国情调”的架构。这些术语有点偏颇。当然,如果您唯一的经验是使用桌面系统,那么这些架构超出了您的经验范围。但是“我以前没见过这个”与“这是晦涩难懂的或异国情调的”是不一样的。对于使用嵌入式系统或 DSP 的人来说,这些事情很常见。 The difference of int8_t, int_least8_t and int_fast8_t?的可能重复 【参考方案1】:

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

【讨论】:

我喜欢你必须想象奇特的架构才能找到一个用例。他们在实践中发现了什么有用的地方吗? @Mehrdad 在 ARM 中,如果你的 int_fast8_t 是 32 位变量,你不需要在算术运算之前做符号扩展。 例如,@Mehrdad MIPS 将任何uintX_fast_t 设为小于 32 位是非常错误的。您甚至不必想象架构会使 uint8_t 未定义,例如 36 位的 UNIVAC,我假设 char 是 9 位的。 @Mehrdad:我承认我从未见过uint_leastX_tuint_fastX_t 在现实世界的应用程序中使用。 uintX_t 是的,它们被大量使用。看起来人们对异域架构的可移植性不是很感兴趣。这是意料之中的,即使您的未签名正确,您的程序也会在一千种不同的事情上失败。 @skyking:我并不是说它们不应该被使用,只是它们在实践中并没有被大量使用。如果您能找到合理使用它们的真实应用程序或库,请发布链接,因为我找不到任何链接。【参考方案2】:

uint8_t 表示:给我一个正好为 8 位的无符号整数。

uint_least8_t 意思是:给我最小类型的无符号整数,它至少有 8 位。优化内存消耗。

uint_fast8_t 表示:给我一个至少 8 位的无符号整数。考虑到对齐的考虑,如果它能让我的程序更快,请选择一个更大的类型。优化速度。

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

【讨论】:

谢谢。很高兴知道stdint.h 中的签名类型保证是二进制补码。想知道在编写可移植代码时它会在哪些方面有所帮助。 请注意,使用 2 的补码格式只需要精确的宽度变体。另请注意,这些不需要存在。因此,平台不需要支持 2 的补码格式。 @legends2k:stdint.h 中的类型在尝试编写可移植代码时比人们可能喜欢的要少得多,因为虽然它们需要使用二进制补码存储格式,但这并不暗示它们将表现出补码包装行为。另请注意,即使在 int 为 32 位的平台上,也不能保证使用 int32_t* 写入值并使用 int* 读取值,反之亦然。 @supercat 我见过的每个编译器都对 stdint.h 类型使用内部 typedef,以使它们与基本的“关键字”整数类型之一同义。因此,如果您担心指针别名,我认为这在实践中不会成为问题,只是在理论上。 @Lundin:有些编译器使用“long”作为 int32_t 的 typedef,有些使用“int”。即使“int”和“long”具有相同的表示形式,出于 C 的别名规则的目的,它们也可能(有时是)被认为是不同的。【参考方案3】:

理论大致如下:

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

uint_least8_t 必须是可以存储至少 8 位的最小可用无符号整数类型。当您想尽量减少大型数组等事物的内存使用时,您会使用它。

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

实践是“快速”和“最少”类型的使用不多。

“最少”类型只有在您关心可移植性以使用 CHAR_BIT != 8 而大多数人不关心的架构时才真正有用。

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

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

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

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

【讨论】:

glibc 的定义是在这些优化不存在的时候选择的,现在它们已融入 ABI 并且无法更改。这是 _least 和 _fast 类型在实践中实际上没有用的几个原因之一。 @zwol:我希望该语言会添加根据布局和语义要求定义的类型类型,例如“我需要它的低位将别名其他 16 位类型,并且可以保存值 0-65535,但我不需要它将更大的值与该范围挂钩”。别名、布局、范围和超出范围的行为应该是一个类型的四个独立方面,但 C 只允许某些在不同平台之间不一致的组合。【参考方案4】:

某些处理器在处理较小数据类型时的效率不如在处理大型数据类型上的效率高。例如,给定:

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

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

【讨论】:

在处理较小的数据类型与处理原生字长的较大数据类型时,额外的可能不必要的指令有助于说明为什么许多“快速”数据类型可能具有更大的位宽度超出预期。谢谢你的例子。 @Galaxy:不幸的是,标准不允许“最小”类型的行为可能因上下文而异。例如,在许多机器上,寄存器中 32 位值的算术运算可能比寄存器中使用 8 位值的运算更快,但 8 位加载和存储的速度与 32 位加载和存储以及缓存的速度相同问题可能会导致 8 位值更有效。【参考方案5】:

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

这应该是显而易见的。这意味着它是一个无符号整数类型,并且它的宽度至少是 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 不需要存在(因为这种小整数类型在某些平台上不存在)。

【讨论】:

【参考方案6】:

“快速”整数类型被定义为可用的最快整数,至少需要所需的位数(在您的情况下为 8)。

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

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

【讨论】:

【参考方案7】:

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

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

【讨论】:

【参考方案8】:

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

【讨论】:

以上是关于uint8_t、uint_fast8_t 和 uint_least8_t 之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

将 uint8_t 数组转换为 C 中的 uint16_t 值

uint8与uint8_t区别

uint8_t 数据类型

PHP - unpack() uint8_t 和 uint16_t

将 int16_t 变量转换为 uint8_t 以传递给函数

将指向 uint16_t 的指针传递给需要 C 中 uint8_t[] 数组的子例程 - 如何?