C语言中的大位数组

C语言中的大位数组,c,linux,bitarray,C,Linux,Bitarray,我们的操作系统教授提到,为了给一个新进程分配一个进程id,内核会在一个数组中递增地搜索第一个零位,该数组的大小等于最大进程数(默认情况下约32768个),其中分配的进程id中存储了1个 据我所知,C语言中没有位数据类型。显然,这里缺少了一些东西 有没有这样的特殊结构,我们可以从中建立一个位数组?这到底是怎么做到的 更重要的是,在这样的数组上可以执行哪些操作?位数组只是用于读取单个位的字节数组 假设您有一个1字节的char变量。它包含8位。您可以通过执行值为1的按位AND操作来测试最低位是否为真,

我们的操作系统教授提到,为了给一个新进程分配一个进程id,内核会在一个数组中递增地搜索第一个零位,该数组的大小等于最大进程数(默认情况下约32768个),其中分配的进程id中存储了1个

据我所知,C语言中没有位数据类型。显然,这里缺少了一些东西

有没有这样的特殊结构,我们可以从中建立一个位数组?这到底是怎么做到的


更重要的是,在这样的数组上可以执行哪些操作?

位数组只是用于读取单个位的字节数组

假设您有一个1字节的
char
变量。它包含8位。您可以通过执行值为1的按位AND操作来测试最低位是否为真,例如:

 char a = /*something*/;
 if (a & 1) {
    /* lowest bit is true */
 }
请注意,这是一个单符号和。它与逻辑AND运算符
&&
完全不同。这是因为
a&1
将“屏蔽”除第一位以外的所有位,因此
a&1
将非零,当且仅当
a
的最低位为1时。类似地,对于连续的二次幂,您可以通过与2和4的和运算来检查第二个最低位是否为真,第三个最低位是否为4,等等

因此,32768元素位数组将表示为4096元素字节数组,其中第一个字节保存位0-7,第二个字节保存位8-15,以此类推。要执行检查,代码将从包含要检查位的数组中选择字节,然后使用逐位操作从字节中读取位值

就操作而言,与任何其他数据类型一样,您可以读取值和写入值。我解释了如何读取上面的值,我将在下面解释如何写入值,但是如果您真的对理解按位操作感兴趣,请阅读我在第一句中提供的链接

写位的方式取决于要写0还是1。要将1位写入字节
a
,执行与AND操作相反的操作:OR操作,例如

 char a = /*something*/;
 a = a | 1; /* or a |= 1 */
在此之后,
a
的最低位将被设置为1,无论是否在之前设置。同样,您可以将其写入第二个位置,将1替换为2,或将其写入第三个位置,将其替换为4,以此类推,表示2的幂

最后,要写入一个零位,您可以使用与要写入的位置相反的和,例如

 char a = /*something*/;
 a = a & ~1; /* or a &= ~1 */

现在,
a
的最低位被设置为0,而不考虑其先前的值。这是因为
~1
会将除最低位设置为1和最低位设置为零之外的所有位都设置为1。这将最低位“屏蔽”为零,并将
a
的剩余位单独保留。

请参见a
struct
可以为成员分配位大小,但这是“C”中“位类型”的范围

其余部分通过逐位操作完成。例如搜索PID位图可以通过以下方式完成:

extern uint32_t *process_bitmap;
uint32_t *p = process_bitmap;
uint32_t bit_offset = 0;
uint32_t bit_test;

/* Scan pid bitmap 32 entries per cycle. */
while ((*p & 0xffffffff) == 0xffffffff) {
  p++;
}

/* Scan the 32-bit int block that has an open slot for the open PID */
bit_test = 0x80000000;
while ((*p & bit_test) == bit_test) {
   bit_test >>= 1;
   bit_offset++;
}
pid = (p - process_bitmap)*8 + bit_offset;

这比使用每个PID一个字节扫描数组的简单for循环快大约32倍。(实际上,大于32倍,因为更多位图将保留在CPU缓存中。)

C中没有位类型,但位操作相当简单。有些处理器有特定于位的指令,下面的代码可以很好地优化这些指令,即使没有这些指令,也应该非常快。使用32位字数组而不是字节数组可能更快,也可能更快。内联而不是函数也有助于提高性能

如果您有内存要消耗,只需使用整个字节存储一位(或整个32位数字,等等),就可以以占用内存为代价大大提高性能

unsigned char data[SIZE]; unsigned char get_bit ( unsigned int offset ) { //TODO: limit check offset if(data[offset>>3]&(1<<(offset&7))) return(1); else return(0); } void set_bit ( unsigned int offset, unsigned char bit ) { //TODO: limit check offset if(bit) data[offset>>3]|=1<<(offset&7); else data[offset>>3]&=~(1<<(offset&7)); } 无符号字符数据[大小]; 无符号字符get_位(无符号整数偏移) { //TODO:限制检查偏移量
如果(data[offset>>3]&(13]|=13]&=~(1但是,如果您使用字符来模拟位数组,则不要对其执行所有操作(例如&2,&4…)听起来太神秘了?只有当你不懂按位运算时它才会神秘,就像任何编程语言或语言功能如果你不懂它就会神秘一样。通常建议不要尝试聪明,不要为了清晰起见而使用位摆弄技巧来代替常规算术,但当你谈论获取和设置时对于单个位,按位操作是唯一的方法。如果您认为可以使代码更可读,则可以将操作包装在函数或宏中,但在某种程度上,您必须使用按位操作来操作位。这就是它们的用途。如果您不习惯,则可能会很神秘,但速度很快!您可以搜索如果您的位数组实际上是一个int数组,请在每一步查看32位,以确定第一个零位。某些CPU体系结构有一条指令,用于在顺序内存位置中查找第一个零位,以及在CPU寄存器中查找第一个清除位或设置位。在过去,数据结构通常针对这种体系结构进行优化eatures.Linux选择更加与CPU无关是出于实际原因,比如支持广泛的CPU体系结构和简化源代码。如果您编写(极其琐碎的)宏来执行位操作,然后使用这些宏访问位数组,这并不神秘。想象一下使用32768字节(32KB)仅用于进程ID的数组!您确定使用这种方案会提高性能吗? unsigned char data[SIZE]; unsigned char get_bit ( unsigned int offset ) { //TODO: limit check offset if(data[offset>>3]&(1<<(offset&7))) return(1); else return(0); } void set_bit ( unsigned int offset, unsigned char bit ) { //TODO: limit check offset if(bit) data[offset>>3]|=1<<(offset&7); else data[offset>>3]&=~(1<<(offset&7)); }