【问题标题】:Can I allocate a specific number of bits in C?我可以在 C 中分配特定数量的位吗?
【发布时间】:2008-11-12 16:33:20
【问题描述】:

我正在尝试存储大量在运行时确定的布尔信息。我想知道最好的方法是什么。

我目前一直在尝试使用以下方式分配内存:

pStatus = malloc((<number of data points>/8) + 1);

认为这会给我足够的工作。然后我可以使用数组表示法中的指针引用每个布尔值:

pStatus[element]

不幸的是,这似乎效果不佳。首先,我很难将内存初始化为整数值0。这可以使用memset() 来完成吗?不过,我认为这不会影响我在尝试访问 pStatus[element] 时崩溃的原因。

我也不完全相信这种方法是最好的方法。我真正想要的本质上是一个反映布尔值状态的巨大位掩码。我错过了什么吗?

【问题讨论】:

  • 多大是多大?一百万位,每个字节存储一个只有 1Mb 的内存。当字节工作得更好时,为什么还要乱用比特?
  • 我认为目前的cmets有点二等公民。但是,这是这个问题的主题:-P
  • 我同意上述评论。仅使用字节数组(无符号字符)会更简单、更快(开发时间和运行时间)。
  • 进一步偏离主题:“将评论转换为答案”可能是正确的 SO 功能。
  • 啊,在维基百科的字节文章中找到的:在现代系统中,字节通常由 8 位组成,但是,字节的大小可以变化,通常由底层计算机操作系统或硬件。从历史上看,字节的范围从 5 位到 12 位不等。需要引用吗?

标签: c malloc bitmask


【解决方案1】:
pStatus = malloc((<number of data points>/8) + 1);

这确实为您的位分配了足够的字节。然而,

pStatus[element]

这会访问元素的第字节,而不是位。因此,当元素超过总位数的八分之一时,您将访问已分配数组的末尾。

我会定义一些辅助函数

int get_bit(int element)
{
    uint byte_index = element/8;
    uint bit_index = element % 8;
    uint bit_mask = ( 1 << bit_index);

    return ((pStatus[byte_index] & bit_mask) != 0);
}

void set_bit (int element)
{
    uint byte_index = element/8;
    uint bit_index = element % 8;
    uint bit_mask = ( 1 << bit_index);

    pStatus[byte_index] |= bit_mask);
}

void clear_bit (int element)
{
    uint byte_index = element/8;
    uint bit_index = element % 8;
    uint bit_mask = ( 1 << bit_index);

    pStatus[byte_index] &= ~bit_mask;
}

(为了清楚起见,对元素范围的错误检查。你也可以制作这个宏)

【讨论】:

  • 关于 get_bit - 虽然它与其他实现非常平行,但您可以先获取字节,然后检查非零位:uchar b = pStatus(byte_index); if (b) { uint bit_windex = element % 8; ... } 其他 { 返回 0; }
  • 为什么? (不是不​​同意,只是想知道为什么)。编译器应该可以很好地处理这两个版本。
  • 你不应该假设一个字节是 8 位。使用 CHAR_BIT 而不是 8,必要时包括 。这似乎适用于该线程中的大多数 cmets。
  • 过去不假设一个字节是 8 位可能是有道理的,但今天似乎有点激进。
  • 根据您的计算机/编译器,使用 32/64 位 [整数] 字执行此操作可能更有效。自 80 年代以来,我的电脑就没有 8 位内存总线。
【解决方案2】:

...认为这会给我足够的工作。然后我可以使用数组表示法中的指针引用每个布尔值:

pStatus[element]

元素正在寻址字节,而不是位。你想要这样的东西:

pStatus[element/8] & (1 << (element % 8))

【讨论】:

【解决方案3】:

小点:要获得足够的内存来存储 N 位,(N/8) + 1 字节是不精确的(可以是一个太多)。

(N+7)/8 总是最小的数。

【讨论】:

  • 我总是使用 (N/8)+1 因为逻辑上它更有意义,但是当空间紧张时 (N+7)/8 是有用的记住(虽然更难理解并且可能更容易错误)。
【解决方案4】:

嗯,最简单的答案是使用 calloc 而不是 malloc。

它被定义为将它分配的内存初始化为零,并且通常可以通过使用页面映射技巧来做到这一点。

这将解决您的内存初始化问题。这里的其他十几个帖子似乎充分解决了索引问题以及您偶尔分配一个额外字节的事实(哦,太可怕了!),所以我不会在这里重复他们的内容。

【讨论】:

    【解决方案5】:

    pStatus[element] 将为您提供该地址的整个字节。

    要设置特定元素,您可以执行以下操作:

    pStatus[element >> 3] |= 1 << (element & 7);
    

    重置元素:

    pStatus[element >> 3] &= ~1 << (element & 7);
    

    并测试一个元素:

    if (pStatus[element >> 3] & (1 << (element & 7)) != 0)
    

    初始分配应该是

    pstatus = malloc((<number of data points> + 7) / 8)
    

    你所拥有的会工作,但偶尔会浪费一个字节

    【讨论】:

      【解决方案6】:

      我不禁注意到,这里 C 中的所有回复似乎都假设一个字节是 8 位。这在 C 语言中不一定是正确的(尽管在大多数主流硬件上当然是正确的),因此在代码中做出这种假设是相当糟糕的形式。

      编写与架构无关的代码的正确方法是

      #include <limits.h>
      

      然后在需要“char 中的位数”的任何地方使用 CHAR_BIT 宏。

      【讨论】:

      • 这是评论,不是答案。
      【解决方案7】:

      让自己更快乐,并定义一个类型和函数来操作该类型。这样,如果您发现位访问太慢,您可以将每个布尔值的内存单位更改为字节/字/长,或者如果内存确实是一个问题(即,如果您的集合大多为零),则可以采用稀疏/动态数据结构,您可以只保留一个包含 1 坐标的列表。

      您可以编写代码以完全不受位向量实现更改的影响。

      【讨论】:

        【解决方案8】:

        pStatus[element] 不寻址该位。它获得的确切字节取决于 pStatus 的类型——我假设 char* 或等效的——所以 pStatus[element] 为您获取元素的第 字节。

        你可以将 memset 设置为 0,是的。

        【讨论】:

          【解决方案9】:
           pStatus = malloc((<number of data points>/8) + 1);
          

          那部分很好。

           pStatus[element]
          

          这就是你遇到麻烦的地方。当你想寻址位时,你就是地址字节。

           pStatus[element / 8 ]  
          

          将为您提供数组中正确的字节。

          【讨论】:

            【解决方案10】:

            你需要分配c = malloc((N+7)/8)字节,你可以设置第n个

             c[n/8]=((c[n/8] & ~(0x80 >> (n%8))) | (0x80>>(n%8)));
            

            清除

             c[n/8] &= ~(0x80 >> (n%8));
            

            和测试

             if(c[n/8] & (0x80 >> (n%8))) blah();
            

            【讨论】:

            • 为什么在第一个方程式中设置它之前清除该位? "& ~(...)" 短语是不必要的。
            【解决方案11】:

            如果您不介意编写包装器,您也可以使用 C++ 的 STL 中的 bit_set 或 bit_vector,看起来它们(尤其是后者)正是您需要的,已经编码、测试和打包(以及大量钟声和口哨声)。

            很遗憾,我们缺乏在 C 应用程序中使用 C++ 代码的直接方式(不,创建包装器对我来说并不简单,也不有趣,而且从长远来看意味着更多的工作)。

            【讨论】:

              【解决方案12】:

              std::vector&lt;bool&gt; 会有什么问题?

              【讨论】:

              • sizeof(bool)=1。 vector 每个字节存储一个条目。 std::vector 消耗额外的开销。它使用 C++ 而不是 C。更好的选择是 bit_vector(每位一个条目)和/或 bitset。参考:sgi.com/tech/stl/bit_vector.html && sgi.com/tech/stl/bitset.html
              • 更正:显然较新的编译器现在专门为 vector 提供每个位一个条目。我的立场是正确的。
              【解决方案13】:

              令我惊讶的是,这里只有一个答案提到了 CHAR_BIT。一个字节通常是 8 位,但并非总是如此。

              【讨论】:

              • 在所有幸存的非嵌入式平台上,CHAR_BIT 为 8。
              • 我做的大部分 C 都是针对嵌入式设备的。 (不可否认,在大多数情况下,CHAR_BITS 仍然是 8)。当 ANSI 标准规定 CHAR_BIT 始终为 8 时,我们可以省略 CHAR_BIT。
              【解决方案14】:

              您的分配代码是正确的,请参阅this answer 中给出的set_bit()get_bit() 函数来访问布尔值。

              【讨论】:

                【解决方案15】:

                如果您仅限于几个位,您可以代替 eananon01 解决方案也使用位域的 c 内置工具(很少有场合可以使用它们,但这是一个)

                对于这个有点重口味的东西,我可以推荐: 赫尼·沃伦斯“黑客之乐”

                【讨论】:

                  【解决方案16】:

                  布尔值在 C 中“从不”是一个单独的值。所以一个结构可能是为了让你继续前进。

                  确实,您不会初始化 mem 区域,因此您需要单独进行。

                  这是一个简单的例子,说明如何使用联合结构和枚举来实现

                  typedef unsigned char           BYTE;
                  typedef unsigned short          WORD;
                  typedef unsigned long int       DWORD;
                  typedef unsigned long long int  DDWORD;
                  enum STATUS
                  {
                      status0 = 0x01,
                      status1 = 0x02,
                      status2 = 0x04,
                      status3 = 0x08,
                      status4 = 0x10,
                      status5 = 0x20,
                      status6 = 0x40,
                      status7 = 0x80,
                  status_group = status0 + status1 +status4
                  };
                  #define GET_STATUS( S ) ( ((status.DDBuf&(DDWORD)S)==(DDWORD)S) ? 1 : 0  )
                  #define SET_STATUS( S ) (  (status.DDBuf|=  (DDWORD)S) )
                  #define CLR_STATUS( S ) (  (status.DDBuf&= ~(DDWORD)S) )
                  static union {
                   BYTE   BBuf[8];
                   WORD   WWBuf[4];
                   DWORD  DWBuf[2];
                   DDWORD DDBuf;
                  }status;
                  
                  int main(void)
                  {
                      // Reset status bits
                      status.BBuf[0] = 0;
                      printf( "%d \n", GET_STATUS( status0 ) );
                  
                      SET_STATUS( status0 );
                      printf( "%d \n", GET_STATUS( status0 ) );
                  
                      CLR_STATUS(status0);
                      printf( "%d \n", GET_STATUS( status0 ) );
                      SET_STATUS( status_group );
                      printf( "%d \n", GET_STATUS( status0 ) );
                      system( "pause" );
                      return 0;
                  }
                  

                  希望这会有所帮助。此示例最多可以处理 64 个状态布尔值,并且可以轻松扩展。

                  此示例基于 Char = 8 bits int = 16 bits long int = 32 bits 和 long long int = 64 bits

                  我现在还添加了对状态组的支持。

                  【讨论】:

                  • 不错的代码!可能的改进:#define GET_STATUS( S ) (status.DWBuf&(DWORD)S) 自动返回预期值(删除多余的比较)。给工会起个名字(请不要匿名)。将宏转换为函数,并让它们接收他们正在处理的联合作为参数。
                  • 另外:您忘记定义宏 WORD。最好使用宏 __int32 和 __int16 定义 DWORD 和 WORD(我相信它们现在是标准的一部分),这样这些元素在所有编译器和平台上的大小始终相同。
                  • @Joe Pineda:__int32 和 __int16 是特定于 MS 的构造。标准中最接近的等价物是 int32_t 和 int16_t(在 中定义,并且(有效地)在 中定义。一般来说,位操作最好使用无符号等价物。
                  • 因为状态变量是 64 位 (unsigned long int)
                  • 错误:WORD 是 16 位长,DWORD 是 32。你需要一个“long long int”或一个 __int64 来获得 64 位,然后并不是所有的编译器都支持这些选项。
                  猜你喜欢
                  • 1970-01-01
                  • 2012-02-08
                  • 2012-05-09
                  • 2020-08-30
                  • 2017-09-02
                  • 2020-10-04
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多