我有一个高度可移植的库(它在任何地方都能编译和工作,即使没有内核),我希望它尽可能保持可移植性。到目前为止,我一直避免使用64位数据类型,但我现在可能需要使用它们-准确地说,我需要一个64位位掩码。
我从来没有真正想过这个问题,我也不是一个硬件Maven(特别是关于嵌入式系统),但我现在想知道:使用uint64_t
(或等价地,uint_least64_t
)有什么不便之处?我可以想到两种方法来回答我的问题:
1.实际可移植性:是否所有微控制器(包括8位CPU)都能处理64位整数?
1.性能:一个8位的CPU对一个64位的整数执行按位操作的速度和对一个32位的整数执行按位操作的速度有多慢?我设计的函数只有一个64位的变量,但是会对它执行很多按位操作(即在一个循环中)。
3条答案
按热度按时间cmssoen21#
C语言允许两种形式的编译器:hosted 和 freestanding。Hosted意味着在操作系统上运行,而freestanding则不需要操作系统。大多数嵌入式系统编译器都是独立的实现。
独立编译器有一些灵活性,它们不需要支持所有的标准库,但它们需要支持它们的最小子集。这包括
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位的类型时,它必须依赖软件库。
因此,如果你在一个8位的bitter上尝试32位的算术,它会将一些编译器软件库与代码内联,结果将是数百条汇编指令,使得这样的代码非常低效和消耗内存。
同样的事情,在缺少FPU的MCU上的浮点数,也会通过软件浮点库生成非常低效的代码。
为了说明,看看这个非优化的代码,在8位AVR(gcc)上进行一些非常简单的64位加法:https://godbolt.org/z/ezbKjY
它实际上支持
uint64_t
,但编译器产生了大量的开销代码,大约100条指令。在中间,调用隐藏在可执行文件中的内部编译器函数call __adddi3
。如果我们启用优化,我们将得到
我们必须深入挖掘库源代码或单步执行程序集,看看
__adddi3
中有多少代码。因此,正如您希望看到的那样,在8位CPU上执行64位运算是一个非常糟糕的想法。
a9wyjsp72#
好吧,如果你主要关心的是保持一个公平的兼容性水平,这就是避免使用64位数字的原因,为什么你不使用一个
int
整数数组,并考虑使用一个完整的整数来存储,比如说,30位。我建议你看一下标准库源,关于使用位掩码(大于32位)来表示例如
select(2)
系统调用所涉及的文件,以及如何使用FDSET
宏。事实上,你可能会遇到这样的问题,即决定是否跨越用于表示位图的数据类型中的32位限制,或者通过使用仍然可用的64位类型来解决这个问题(暂时)。当你遇到64位位位掩码时,这将是下一个规模问题,最终你将不得不跨越这条线。
你现在可以做这个练习,你会学到数据类型最终是一个或多或少的位集合,你可以给予他们任何你想要的用途。你计划使用80位
long double
值来存储大于64位的位掩码吗?我想你不会,所以考虑数组解决方案,这可能会一劳永逸地解决你的问题。如果你的问题是我的情况,我会写一个32位无符号数的数组,所以所有的位在移位,位操作等方面都是一样的。
在上面的例子中,
FDSET_TYPE
宏允许你声明一个你作为第二个参数传递的位数变量,并使用一个无符号的32位整数数组来实现它,向上舍入到下一个值以允许包括所有位。FDSET_ISSET(name, 35)
计算所请求的位所在的像元和偏移量,并使用除以所传递的数字的余数对其进行掩码32 ---但是当我们选择2的幂时,y使用0x1f
的掩码来屏蔽数字的最后5位以获得余数mod 32)。mwngjboj3#
我已经使用Arduino Mega compiler on Godbolt测试了64位按位AND的四种变体。
结果如下:
uint64_t
操作数的按位AND:uint32_t
操作数对的逐段按位ANDuint16_t
操作数的四元组进行逐段按位AND运算。uint8_t
操作数的八元组进行逐段按位AND运算。所以我无法击败编译器的合成64位按位AND。注意,按值传递结构体会明显增加更多的指令。
如果你主要需要做的是检查单个位是否被设置或重置,那么上面的测试不会很好地模拟你的用例。检查单个位是否被设置比计算整个按位AND结果需要的工作要少得多!
所以我尝试了5种方法来检查一个64位的集合中是否有一位是set on Godbolt。
结果:
uint64_t
整数中是否设置了位uint32_t
操作数中是否设置了一个位uint16_t
操作数的四元组中设置了位。uint8_t
操作数的八元组中设置了位。所以这个故事的寓意是:让编译器为编译器支持的任何字长的按位运算操心吧!