【问题标题】:How to define and work with an array of bits in C?如何在 C 中定义和使用位数组?
【发布时间】:2011-02-01 06:58:09
【问题描述】:

我想创建一个非常大的数组,在上面写“0”和“1”。我正在尝试模拟一种称为随机顺序吸附的物理过程,其中长度为 2 的单位(二聚体)被沉积在随机位置的 n 维晶格上,而不会相互重叠。当晶格上没有更多空间用于沉积更多二聚体(晶格被堵塞)时,该过程停止。

最初我从零格开始,二聚体由一对“1”表示。随着每个二聚体的沉积,二聚体左侧的位点被阻塞,因为二聚体不能重叠。所以我通过在格子上放置三个'1'来模拟这个过程。我需要多次重复整个模拟,然后计算出平均覆盖率。

我已经使用 1D 和 2D 晶格的字符数组完成了这项工作。目前,在处理 3D 问题和更复杂的概括之前,我正在尝试使代码尽可能高效。

这基本上是一维代码的样子,简化了:

int main()
{
    /* Define lattice */
    array = (char*)malloc(N * sizeof(char));

    total_c = 0;

    /* Carry out RSA multiple times */
    for (i = 0; i < 1000; i++)
        rand_seq_ads();

    /* Calculate average coverage efficiency at jamming */
    printf("coverage efficiency = %lf", total_c/1000);

    return 0;
}

void rand_seq_ads()
{
    /* Initialise array, initial conditions */
    memset(a, 0, N * sizeof(char));
    available_sites = N;
    count = 0;

    /* While the lattice still has enough room... */
    while(available_sites != 0)
    {
        /* Generate random site location */
        x = rand();

        /* Deposit dimer (if site is available) */
        if(array[x] == 0)
        {
            array[x] = 1;
            array[x+1] = 1;
            count += 1;
            available_sites += -2;
        }

        /* Mark site left of dimer as unavailable (if its empty) */
        if(array[x-1] == 0)
        {
            array[x-1] = 1;
            available_sites += -1;
        }
    }

    /* Calculate coverage %, and add to total */
    c = count/N
    total_c += c;
}

对于我正在做的实际项目,它不仅涉及二聚体,还涉及三聚体、四聚体以及各种形状和大小(用于 2D 和 3D)。

我希望我能够使用单个位而不是字节,但我一直在阅读,据我所知,您一次只能更改 1 个字节,所以要么我需要这样做一些复杂的索引还是有更简单的方法来做到这一点?

感谢您的回答

【问题讨论】:

  • 在处理单个位时请注意:如果效率至关重要,您可能希望在可能的情况下一次至少在一个字节上应用您的操作(即查看多个坐标同时),因为这样做,如果做得对,不会花费任何额外费用。除了代码的瓶颈部分之外,这样做可能不值得麻烦。

标签: c arrays multidimensional-array bit-manipulation bitarray


【解决方案1】:

您可以使用 &(按位与)和

例如,(1

char eightBits = 0;

//Set the 5th and 6th bits from the right to 1
eightBits &= (1 << 4);
eightBits &= (1 << 5);
//eightBits now looks like "00110000". 

然后用一个字符数组将其放大,并首先找出要修改的适当字节。

为了提高效率,您可以提前定义一个位域列表并将它们放入一个数组中:

#define BIT8 0x01
#define BIT7 0x02
#define BIT6 0x04
#define BIT5 0x08
#define BIT4 0x10
#define BIT3 0x20
#define BIT2 0x40
#define BIT1 0x80

char bits[8] = {BIT1, BIT2, BIT3, BIT4, BIT5, BIT6, BIT7, BIT8};

然后你避免了位移的开销,你可以索引你的位,把前面的代码变成:

eightBits &= (bits[3] & bits[4]);

或者,如果您可以使用 C++,则可以只使用 std::vector&lt;bool&gt;,它在内部定义为位向量,并带有直接索引。

【讨论】:

  • 使用std::vector&lt;bool&gt; 不会让他获得最佳性能,因为他最终将进行两次查找以获得一对位。这种惩罚是否足以证明创建自己的std::vector&lt;bool&gt; 变体是合理的,这取决于查找(和分配)本身是否是瓶颈。
  • 假设 C++ 是一个选项(OP 只提到了 C),我会毫不犹豫地从 std::vector&lt;bool&gt; 开始,只是为了简洁和可读性。如果我需要更好的性能,我会分析以找出瓶颈所在。 (它很可能在 rand() 而不是向量查找)。
  • 你可以用char bits[8] = { ... };代替#define bits(x) BIT##x
  • 我认为你的意思是 |= 和 |而不是 &= 和 &。
  • “为了提高效率” ⇒ 这取决于架构。也许有时 shift 可能比数组访问便宜。换句话说,这是一个非常小的“改进”,如果有的话。除非你真的需要,否则不要担心。 过早的优化是万恶之源。
【解决方案2】:

这是一个权衡:

(1) 每个 2 位值使用 1 个字节 - 简单、快速,但使用 4x 内存

(2) 将位打包成字节 - 更复杂,一些性能开销,使用最少的内存

如果你有足够的可用内存,那么选择 (1),否则考虑 (2)。

【讨论】:

  • @Paul:不,它使用 4 倍的内存,因为他将在 1 个字节中存储 2 位数字。但是,我认为从 OP 的问题来看,他已经决定使用 (2)。
  • @Brian:谢谢 - 我错过了那部分 - 我会相应地更新我的答案。
【解决方案3】:
typedef unsigned long bfield_t[ size_needed/sizeof(long) ];
// long because that's probably what your cpu is best at
// The size_needed should be evenly divisable by sizeof(long) or
// you could (sizeof(long)-1+size_needed)/sizeof(long) to force it to round up

现在,bfield_t 中的每个 long 都可以保存 sizeof(long)*8 位。

您可以通过以下方式计算所需大的索引:

bindex = index / (8 * sizeof(long) );

和你的位数

b = index % (8 * sizeof(long) );

然后您可以查找您需要的长度,然后从中屏蔽掉您需要的位。

result = my_field[bindex] & (1<<b);

result = 1 & (my_field[bindex]>>b); // if you prefer them to be in bit0

第一个在某些 cpu 上可能更快,或者可以节省您转移所需的时间 在多个位数组中的同一位之间执行操作。它也反映 字段中位的设置和清除比第二种实现更紧密。 设置:

my_field[bindex] |= 1<<b;

明确:

my_field[bindex] &= ~(1<<b);

您应该记住,您可以对保存字段的 long 使用按位运算 这与对单个位的操作相同。

如果可用,您可能还想研究 ffs、fls、ffc 和 flc 函数。 ffs 应始终在strings.h 中可用。它只是为了这个目的而存在的——一串位。 无论如何,它是找到第一组,本质上是:

int ffs(int x) {
    int c = 0;
    while (!(x&1) ) {
        c++;
        x>>=1;
    }
    return c; // except that it handles x = 0 differently
}

对于处理器来说,这是一种常见的操作,它有一条指令,你的编译器可能会生成该指令,而不是像我写的那样调用一个函数。顺便说一句,x86 对此有一个说明。哦,ffsl 和 ffsll 是同一个函数,只是分别需要 long 和 long long。

【讨论】:

  • 一个字节不一定是8位长!从技术上讲,bfield_t 中的每个long 都可以保存CHAR_BIT * sizeof (long) 位,而不是8 * sizeof (long) 位,只是在许多架构上CHAR_BIT 等于8。
【解决方案4】:

bitarray.h

#include <inttypes.h> // defines uint32_t

//typedef unsigned int bitarray_t; // if you know that int is 32 bits
typedef uint32_t bitarray_t;

#define RESERVE_BITS(n) (((n)+0x1f)>>5)
#define DW_INDEX(x) ((x)>>5)
#define BIT_INDEX(x) ((x)&0x1f)
#define getbit(array,index) (((array)[DW_INDEX(index)]>>BIT_INDEX(index))&1)
#define putbit(array, index, bit) \
    ((bit)&1 ?  ((array)[DW_INDEX(index)] |= 1<<BIT_INDEX(index)) \
             :  ((array)[DW_INDEX(index)] &= ~(1<<BIT_INDEX(index))) \
             , 0 \
    )

用途:

bitarray_t arr[RESERVE_BITS(130)] = {0, 0x12345678,0xabcdef0,0xffff0000,0};
int i = getbit(arr,5);
putbit(arr,6,1);
int x=2;            // the least significant bit is 0
putbit(arr,6,x);    // sets bit 6 to 0 because 2&1 is 0
putbit(arr,6,!!x);  // sets bit 6 to 1 because !!2 is 1

编辑文档:

"dword" = "double word" = 32 位值(无符号,但这并不重要)

RESERVE_BITS: number_of_bits --> number_of_dwords
    RESERVE_BITS(n) is the number of 32-bit integers enough to store n bits
DW_INDEX: bit_index_in_array --> dword_index_in_array
    DW_INDEX(i) is the index of dword where the i-th bit is stored.
    Both bit and dword indexes start from 0.
BIT_INDEX: bit_index_in_array --> bit_index_in_dword
    If i is the number of some bit in the array, BIT_INDEX(i) is the number
    of that bit in the dword where the bit is stored.
    And the dword is known via DW_INDEX().
getbit: bit_array, bit_index_in_array --> bit_value
putbit: bit_array, bit_index_in_array, bit_value --> 0

getbit(array,i) 获取包含位 i 的 dword,然后 移动 dword ,以便位 i 成为最低有效位。然后,带有 1 的 按位与 清除所有其他位。

putbit(array, i, v)首先检查v的最低有效位;如果是0,我们要清除该位,如果是1,我们要设置它。
要设置该位,我们对包含该位的 dword 进行 按位或,并将 1 的值 左移 bit_index_in_dword:该位已设置,其他位执行不变。
为了清除该位,我们对包含该位的 dword 和 按位补码进行 按位与 1 左移 bit_index_in_dword:即value 将所有位设置为 1,除了我们要清除的位置中唯一的零位。
宏以, 0 结尾,否则它将返回存储位 i 的 dword 的值,并且该值没有意义。也可以使用((void)0)

【讨论】:

  • 为什么只是 uint32_t,而不是 uint64_t?
  • @DennisV.R.代码很旧,它是为在 32 位嵌入式系统上执行实时任务而编写的。由于大部分变量都是int大小的(包括指针),当你分配1个字节时,通常会浪费3个字节,所以分配字节没有意义。 OTOH,使用 32 位 CPU,一个 64 位 AND 实现为两个 32 位 AND,因此使用 64 位整数没有意义。此外,uint64 可能需要 8 字节对齐(这取决于架构和编译器)。所以选择了 32 位整数。
  • @DennisV.R.顺便说一句,今天我可能会使用uint_fast32_t 代替bitarray_t,并将5 替换为定义为#define DW_INDEX_BITS (3+__builtin_ctz(sizeof(bitarray_t))) 的DW_INDEX_BITS。 0x1f 将变为((1&lt;&lt;DW_INDEX_BITS)-1)。并且代码必须在进行此类更改后进行测试。 __builtin_ctz() 是一个常量表达式(!),但它是 gcc 特定的。嵌入式系统的板可能伴随着相当旧的编译器(当板是新的时,编译器可能相当新)。
  • @18446744073709551615 不应该将putbit 中那些左移的uint32_t 转换为uint32_t,因为index31
【解决方案5】:

如果我还不算太晚,this 页面提供了很棒的示例解释。

int 的数组可以用来处理bits 的数组。假设int 的大小为4 bytes,当我们谈论int 时,我们正在处理32 bits。假设我们有int A[10],意味着我们正在处理10*4*8 = 320 bits,下图显示了它:(数组的每个元素有4个大块,每个代表一个byte,每个较小的块代表一个bit )

因此,设置数组A 中的kth 位:

// NOTE: if using "uint8_t A[]" instead of "int A[]" then divide by 8, not 32
void  SetBit( int A[],  int k )
{
    int i = k/32;        //gives the corresponding index in the array A
    int pos = k%32;      //gives the corresponding bit position in A[i]

    unsigned int flag = 1;   // flag = 0000.....00001

    flag = flag << pos;      // flag = 0000...010...000   (shifted k positions)

    A[i] = A[i] | flag;      // Set the bit at the k-th position in A[i]
}

或缩短版

void  SetBit( int A[],  int k )
{
    A[k/32] |= 1 << (k%32);  // Set the bit at the k-th position in A[i]
}

类似于清除kth 位:

void  ClearBit( int A[],  int k )                
{
    A[k/32] &= ~(1 << (k%32));
}

并测试kth 位:

int TestBit( int A[],  int k )
{
    return ( (A[k/32] & (1 << (k%32) )) != 0 ) ;     
}

如上所述,这些操作也可以写成宏:

// Due order of operation wrap 'k' in parentheses in case it
// is passed as an equation, e.g. i + 1, otherwise the first
// part evaluates to "A[i + (1/32)]" not "A[(i + 1)/32]"
#define SetBit(A,k)     ( A[(k)/32] |= (1 << ((k)%32)) )
#define ClearBit(A,k)   ( A[(k)/32] &= ~(1 << ((k)%32)) )
#define TestBit(A,k)    ( A[(k)/32] & (1 << ((k)%32)) )

【讨论】:

  • 在决定是否使用函数或宏以提高效率时,值得比较编译器生成的机器代码以查看是否存在差异(例如“gcc -O2 -S”。如果调用这些来自其他模块,请参阅stackoverflow.com/questions/5987020/…)。如果编译器足够好,在顶级优化级别,为函数生成的代码应该等同于宏。坚持使用函数的好处是它们更容易被编辑器、调试器(处于较低优化级别)和人类理解。
  • int 的大小取决于您的编译器。不要假设 int 是 4 个字节。查看。在小型 micros 上,一个 int 可能是 16 位。
  • 1) 在处理位时使用 unsigned int 而不是 int,2) 使用 sizeof(unsigned)*CHAR_BIT 而不是 32,或者 3) 简单地使用 uint32_t 会更有意义. unsigned int/sizeof(unsigned) 如果您想支持具有不同 int 大小的架构,而访问 32 位 int 需要多于 1 条指令,那么 sizeof(unsigned) 可能是一个更好的主意。
  • 是的,我同意,但我只是重新提交了链接上给出的内容,以防该链接不再可访问(事实上,这是有人要求的,现在该评论已被删除跨度>
  • TestBit 中的x != 0 是必要的还是它有什么好处?
猜你喜欢
  • 2015-12-18
  • 1970-01-01
  • 1970-01-01
  • 2014-12-27
  • 2011-01-13
  • 2012-07-27
  • 2018-11-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多