在位数组中使用哪种数据类型

时间:2018-12-23 21:19:01

标签: c data-structures bitset bitarray

在创建位数组或在C中调用位集时,应使用哪种数据类型?我应该使用int数组吗? unsigned intsize_tuint8_tuintmax_t

对我来说,使用有符号整数类型是不可以的,正如SO中其他有关有符号整数左右移位的答案(我丢失了答案的链接)所指出的那样。

现在,我应该使用最小的可用无符号整数还是最大的无符号整数?哪一个表现最好?

我的一些想法:SO中的大多数位数组都使用charuint8_t数组,但我看不出这比使用{{1} }(也是因为我还没有看到任何关于为什么要这样做的论点,因此还是这个问题)。当进行某些操作(例如两个位数组之间的并集和交集)时,使用较大的无符号整数时,循环将迭代次数减少。

编辑:看到一些答案后,我认为有些人对我的要求感到困惑。对于那个很抱歉。我想做的是制作一个位数组,其中每个位都可以单独访问或设置为1或0。我知道我可以只使用bool数组,但这并不节省空间。您可以说,如今我们的RAM足够大,位数组相对于布尔数组的增益很小,但这不是重点。 我想知道的是,给定一个位数组,其中每个位都可以由uintmax_t进行修改或访问(与数组的索引不同),我的数组应该是哪种数据类型?

6 个答案:

答案 0 :(得分:2)

这取决于您需要跟踪多少位,访问单个位的效率以及跟踪所有这些位要消耗的内存。

有很多方法可以做到,而无需进一步详细说明,这很难回答。

我看到的是一个简单的uint32_t数组,以保持打包和良好的访问速度。然后,当访问一个位时,假设位128,这将是数组第4个uint32_t的位0。

答案 1 :(得分:2)

我个人会使用bsf。对于大多数(不是全部,但可能是您关心的所有平台)平台,它的大小与CPU寄存器的大小相同,这意味着对于许多需要扫描整个位向量的操作而言,它可以实现最大位数每个循环迭代进行处理(例如查找设置位,计数位等)。对于此类算法,CPU内置clz(向前扫描位)和unsigned long(计数前导零)将大大加快算法的运行速度。

就上下文而言,Linux内核使用size_t作为位向量,其AFAIK与所有Linux ABI上的alert()相同,但在Windows上却并非如此(至少不适用于MSVC)即使在x64上也为32位。

答案 2 :(得分:1)

  

在位数组中使用哪种数据类型(?)
  ...每个位都可以单独访问或设置为1或0。
  ...只能使用布尔数组,但是空间效率不高。

您无法获得所需的一切:需要权衡。


对于N位“数组”,各种方法

_Bool的数组:_Bool ar1[N];

  • 专业版:易于索引:ar1[i]
  • Pro:仅2个值。
  • 缺点:空间效率低下-甚至比unsigned char ar2[N];
  • 更糟

最小类型的数组:unsigned char ar2[N];

  • 专业版:易于索引:ar2[i]
  • 专业人士:没有陷阱值,也没有填充。
  • 骗局:可以编码值0,1和其他值。
  • 缺点:空间效率低

打包的unsigned char数组:unsigned char ar3[(N+CHAR_BIT-1)/CHAR_BIT];

  • Pro:节省空间。
  • 专业人士:没有陷阱值,也没有填充。
  • 缺点:需要帮助代码才能索引:(ar3[i/CHAR_BIT] >> (i%CHAR_BIT))%2
  • 缺点:可能还有一些额外的“数组”元素。

打包的unsigned数组:unsigned ar4[(N+UNSIGNED_BIT-1)/UNSIGNED_BIT];

  • Pro:节省空间。
  • 专业人士:使用本机ar3类型可能比unsigned快/快。
  • 缺点:需要帮助代码才能索引:(ar4[i/UNSIGNED_BIT] >> (i%UNSIGNED_BIT))%2
  • 缺点:可能还有一些额外的“数组”元素。
  • ***:由于unsigned必须基于UNSIGNED_BIT而不是UNSIGNED_MAX,因此人们担心CHAR_BIT可能会被填充会导致更复杂的位宽确定。

结论

IMO,使用_Bool ar1[N];,直到显示出空间/速度有问题为止。在这种情况下,我将移至unsigned ar4[(N+UNSIGNED_BIT-1)/UNSIGNED_BIT];


  

对我来说,使用整数类型是不可以的,正如SO中有关带符号整数左右移位的其他答案所述

在此夸大了OP的担忧。主要的移位问题出现在 signed 类型上。改用 unsigned 类型。


  

使用charuint8_t的数组,但是我看不出这比使用uintmax_t更好。

大概OP在这里占用了一个“打包”的位数组。

  • Con for uintmax_t。它要求数组大小是uintmax_t的位大小与更容易匹配的uint8_t的倍数。否则,两种方式都会浪费内存,uint8_t只会减少内存消耗。

  • Con for uint8_t。它并不总是可用(这是例外)。

  • Con for char。它可能已签名

  • Con for uint8_t。大概比unsigned慢或慢。

  • Con for uintmax_t。除非代码本身支持该宽类型,否则发出的代码可能比其他替代方法慢。

  • Con for uintmax_t。宽类型更可能需要多个指令来缩小类型。当然,平台之间的差异很大。

最好使用最广泛的本机类型-通常为unsigned

IMO,unsigned是包装的上乘之选。

答案 3 :(得分:1)

最好的选择是对尽可能多的方案进行基准测试。根据您打算存储多少位以及读取和写入它们的频率,将每个位存储为unsigned char(甚至存储在unsigned int中)可能很有意义,但是将其中的16个更紧密地包装在16位以上的unsigned int中,这对于效率和易于访问的良好折衷是有意义的。 unsigned int是一个不错的选择,但我不建议您使用unsigned int,除非您可以保证您想要的体系结构不使用填充位或任何意外陷阱值。任何现代架构都可能有uint32_t(在stdint.h中定义),如果您不信任unsigned int,这是我的建议,因为您知道它的确切大小并且可以保证没有填充位按标准。如果您知道将在64位体系结构上运行代码,那么uint64_t将是一个更好的选择。如果可能,请记住进行基准测试。

请注意,该标准要求将小于int的类型上的所有操作隐式转换(在C抽象机中)为int(或unsigned int,如果不合适) int中的内容,然后再次转换回原始_Boolcharshort。有时这可能会导致意外。

答案 4 :(得分:1)

通常,处理单个位时最有效的大小可能是unsigned int。最大的大小和寄存器的大小可能效率低下(例如,在64位80x86上,64位指令需要“ REX前缀”,这将导致毫无意义的膨胀)。

对于处理整个位集(例如搜索,计数),如果性能首先是重要的,那么大小基本上就无关紧要。例如(对于SSE2),您可以将16个8位整数打包到128位寄存器中,或者将8个16位整数打包到128位寄存器中,或者将4个32位整数打包到128位寄存器中,或者将2个打包将64位整数放入128位寄存器中;对于所有这些情况,无论单个整数的大小如何,您都将执行128位运算。

但是,效率并不是唯一重要的事情,使用“不固定大小的整数”(例如unsigned int)意味着您需要使用宏/ #define来污染您的代码很难阅读(在“哦,该死,我需要集中精力,追踪埋在头文件中的一些随机噪声,只是为了看看FOO到底是什么”),而固定大小整数类型(例如uint32_t)将避免这种情况。具体来说,我会使用(并且已经使用过)uint32_t而不关心性能。

  

您可以说,如今我们的RAM足够大,位数组比布尔数组的增益很小,但这不是重点。

您可以说RAM很大且相对较慢,缓存较小且相对较快,并且性能要求通过打包最多的数据来最小化缓存未命中(以提高缓存的效率并减少相对慢的RAM的使用)进入最小的空间。 ;)

答案 5 :(得分:0)

你是对的。通常对位数组使用charunsigned char。这背后的原因纯粹是与效率有关。 char仅保留1字节(8位)的内存,而int通常需要4字节(32位,这取决于您的系统和编译器) 你做数学。您只需要存储一个位,那么哪一个使用效率更高?