繁体   English   中英

使用 uint64_t 的不便

[英]Inconveniences of using uint64_t

我有一个高度可移植的库(即使没有内核,它也可以在任何地方编译和运行良好),我希望它尽可能保持可移植性。 到目前为止,我已经避免使用 64 位数据类型,但我现在可能需要使用它们——准确地说,我需要一个 64 位位掩码。

我从来没有真正考虑过,我还不够硬件专家(尤其是在嵌入式系统方面),但我现在想知道:使用uint64_t (或等效地, uint_least64_t )有哪些不便之uint_least64_t 我可以想到两种方法来解决我的问题:

  1. 实际可移植性:是否所有微控制器(包括 8 位 CPU)都能够处理 64 位整数?
  2. 性能:与 32 位整数相比,8 位 CPU 对 64 位整数执行按位运算的速度有多慢? 我正在设计的函数将只有一个 64 位变量,但会对其执行大量按位运算(即在循环中)。

对符合标准的 C 编译器有各种最低要求。 C 语言允许两种形式的编译器:托管独立 Hosted 旨在运行在 OS 之上,而独立运行则无需 OS。 大多数嵌入式系统编译器都是独立的实现。

独立编译器有一些回旋余地,它们不需要支持所有标准库,但需要支持它们的最小子集。 这包括stdint.h (参见 C17 4/6)。 这反过来又要求编译器实现以下内容(C17 7.20.1.2/3):

需要以下类型:

int_least8_t int_least16_t int_least32_t int_least64_t
uint_least8_t uint_least16_t uint_least32_t uint_least64_t

所以微控制器编译器不需要支持uint64_t ,但它必须(奇怪的是)支持uint_least64_t 实际上,这意味着编译器也可以添加uint64_t支持,因为在这种情况下它是相同的。

至于 8 位 MCU 支持什么……它通过指令集支持 8 位算术,在某些特殊情况下还支持一些使用索引寄存器的 16 位运算。 但一般来说,每当使用大于 8 位的类型时,它都必须依赖软件库。

所以如果你在 8bit 上尝试 32 位算术,它会将一些编译器软件库与代码内联,结果将是数百条汇编指令,使得这些代码非常低效和消耗内存。 64位会更糟。

与缺乏 FPU 的 MCU 上的浮点数相同,这些也会通过软件浮点库生成极其低效的代码。


为了说明这一点,请看一下在 8 位 AVR (gcc) 上进行一些非常简单的 64 位加法的未优化代码: https : //godbolt.org/z/ezbKjY
它实际上支持uint64_t但编译器产生了大量的开销代码,大约 100 条指令。 在它中间,调用了隐藏在可执行文件中的内部编译器函数call __adddi3

如果我们启用优化,我们会得到

add64:
        push r10
        push r11
        push r12
        push r13
        push r14
        push r15
        push r16
        push r17
        call __adddi3
        pop r17
        pop r16
        pop r15
        pop r14
        pop r13
        pop r12
        pop r11
        pop r10
        ret

我们将不得不深入挖掘库源或单步执行程序集,以查看__adddi3有多少代码。 我猜它仍然不是一个微不足道的功能。

因此,正如您所希望的那样,在 8 位 CPU 上执行 64 位算术是一个非常糟糕的主意。

好吧,如果您主要关心的是保持相当水平的兼容性,这就是避免使用 64 位数字的原因,为什么不使用int整数数组,并考虑使用一个完整的整数来存储,比如说,30位。

我建议您查看有关使用位掩码(大于 32 位)来表示例如select(2)系统调用所接触的文件以及如何使用FDSET宏的标准库源。

事实是,您可能会遇到问题,即决定是在用于表示位图的数据类型中跨越 32 位的限制,还是通过使用仍然可用的 64 位类型(暂时)解决该问题。 当您获得 64 位位掩码时,这将是下一个规模问题,并且您最终将不得不越界。

作为练习,您现在可以这样做,您将了解到最后的数据类型或多或少是一组大的位,您可以随意使用它们。 您是否打算使用 80 位long double精度值来存储大于 64 位的位掩码? 我认为你不会,所以想想阵列解决方案,这可能会一劳永逸地解决你的问题。

如果你的问题是我的情况,我会写一个 32 位无符号数的数组,所以所有位在移位、位操作等方面的行为都是一样的。


#define FDSET_TYPE(name, N)  unsigned int name[((N) + 31U) >> 5]
#define FDSET_ISSET(name, N) ((name[(N) >> 5] & 1 << (N & 0x1f)) != 0)

...

    FDSET_TYPE(name, 126);

...

    if (FDSET_ISSET(name, 35)) { ...

在上面的示例中, FDSET_TYPE宏允许您声明作为第二个参数传递的位数的变量,并使用无符号 32 位整数数组实现它,四舍五入到下一个值以允许包含所有位. FDSET_ISSET(name, 35)计算单元格和请求位所在的偏移量,并用您传递的数字除以 32 的余数对其进行掩码 --- 但是当我们选择 2 的幂时,y 使用掩码0x1f屏蔽数字的最后 5 位以获得余数 mod 32)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM