【问题标题】:Creating bitflag variables with large amounts of flags or how to create large bit-width numbers创建具有大量标志的位标志变量或如何创建大位宽数字
【发布时间】:2013-05-01 13:13:01
【问题描述】:

假设我有一个枚举,其位标志选项大于标准数据类型中的位数:

enum flag_t {
FLAG_1 = 0x1,
FLAG_2 = 0x2,
...
FLAG_130 = 0x400000000000000000000000000000000,
};

这是不可能的,原因有几个。枚举的最大大小为 128 位(在我的实验系统上的 C/gcc 中),单个变量的最大大小也为 128 位等。

在 C 中,您不能对数组执行按位运算,但在 C++ 中,我想您可以重载按位运算符以通过循环来完成这项工作。

除了手动记住哪些标志在哪里可以对大数进行这项工作之外,C 中还有其他方法吗?

【问题讨论】:

  • 定义large numbers。你会怎么说,检查这样的large number 是否在第 192 位包含标志?
  • 您将如何使用这些大于任何支持的整数类型的标志?
  • 可能表明使用枚举不是最佳选择。我在想象一些强大的 switch() 声明。
  • @JV:是的,您可以将union 几个整数转换为large number,我想您可以使用它。但是,我还怀疑您必须编写自己的 testForFlag() 方法来确定这些内部整数中的哪些要测试标志。
  • 我会质疑设计。

标签: c bitflags


【解决方案1】:

这正是位域的用途。

在 C 中,可以定义以下数据布局:

struct flag_t 
{
     unsigned int flag1 : 1;
     unsigned int flag2 : 1;
     unsigned int flag3 : 1;
(...)
     unsigned int flag130 : 1;
(...)
     unsigned int flag1204 : 1;   // for fun
};

在本例中,所有标志只占用一位。一个明显的优势是无限数量的标志。另一个很大的优势是您不再局限于单位标志,您可以在中间合并一些多值标志。

但最重要的是,测试和归因会有所不同,并且可能会简化,就单元操作而言:您不再需要进行任何屏蔽,只需通过命名直接访问标志即可。顺便说一下,利用这个机会给这些标志起更全面的名字:)

【讨论】:

  • 这正是我的想法! (使用uchar而不是uint不是更有效吗?)
  • 忽略打包(无论如何这将在实践中消除这种好处)sizeof 显示带有 unsigned char x:1; 的 4 个标志占用 1 个字节,而带有 unsigned int x:1 的 4 个标志占用 4 个(因为 int 是 4 个字节long) 编辑: 这似乎非常有情境,在 int 位标志之后放置的 short 将被“折叠”到 int 中,但将其更改为 int 将产生预期的 8 个字节。
  • 当然,char 类型为您提供了以字节为单位精确定义的 struct 的大小,但是由于在大多数情况下编译器会尝试在内存中对齐您的数据,因此不太可能能够使用 1 到 4 之间的剩余字节。除非您将此结构显式打包到另一个结构中。
【解决方案2】:

在回答与位标志相关的问题Bit Manipulation and Flags 时,我提供了一个使用unsigned char 数组的示例,这是一种用于处理非常大的位标志集的方法,我将在此发布。

此源示例提供以下内容:

  • 为 bitflag 值定义的一组预处理器
  • 一组用于操作位的预处理器宏
  • 在数组上实现按位运算的几个函数

对此的一般方法如下:

  • 为指定数组偏移量和位模式的标志创建一组定义
  • 为适当大小的 unsigned char 数组创建 typedef
  • 创建一组实现按位逻辑运算的函数

答案中的细节以及一些改进和更多说明

使用一组 C 预处理器定义来创建一组与数组一起使用的位标志。这些位标志定义了指定unsigned char 数组中的偏移量以及要操作的位。

此示例中的定义是 16 位值,其中高字节包含数组偏移量,低字节包含unsigned char 数组字节的位标志,其偏移量在高字节中。使用这种技术,您可以拥有最多 256 个元素、256 * 8 或 2,048 个位标志的数组,或者通过从 16 位定义到 32 位 long 您可以拥有更多。 (在 cmets 中,第 0 位表示字节的最低有效位,第 7 位表示字节的最高有效位)。

#define ITEM_FLG_01  0x0001     // array offset 0, bit 0
#define ITEM_FLG_02  0x0002     // array offset 0, bit 1
#define ITEM_FLG_03  0x0101     // array offset 1, bit 0
#define ITEM_FLG_04  0x0102     // array offset 1, bit 1
#define ITEM_FLG_05  0x0201     // array offset 2, bit 0
#define ITEM_FLG_06  0x0202     // array offset 2, bit 1
#define ITEM_FLG_07  0x0301     // array offset 3, bit 0
#define ITEM_FLG_08  0x0302     // array offset 3, bit 1
#define ITEM_FLG_10  0x0908     // array offset 9, bit 7

接下来,您将使用一组宏来设置和取消设置位以及 typedef,以使其更易于使用。不幸的是,在 C 中使用 typedef 并不能为您提供更好的编译器类型检查,但它确实使它更易于使用。这些宏不检查它们的参数,因此您可能会觉得使用常规函数更安全。

#define SET_BIT(p,b) (*((p) + (((b) >> 8) & 0xf)) |= (b) & 0xf)
#define TOG_BIT(p,b) (*((p) + (((b) >> 8) & 0xf)) ^= (b) & 0xf)
#define CLR_BIT(p,b) (*((p) + (((b) >> 8) & 0xf)) &= ~ ((b) & 0xf))
#define TST_BIT(p,b) (*((p) + (((b) >> 8) & 0xf)) & ((b) & 0xf))

typedef unsigned char BitSet[10];

使用这个基本框架的一个例子如下。

BitSet uchR = { 0 };
int  bValue;

SET_BIT(uchR, ITEM_FLG_01);
bValue = TST_BIT(uchR, ITEM_FLG_01);
SET_BIT(uchR, ITEM_FLG_03);
TOG_BIT(uchR, ITEM_FLG_03);
TOG_BIT(uchR, ITEM_FLG_04);
CLR_BIT(uchR, ITEM_FLG_05);
CLR_BIT(uchR, ITEM_FLG_01);

接下来您可以引入一组实用函数来执行我们想要支持的一些按位运算。这些按位运算类似于内置的 C 运算符,例如按位或 (|) 或按位与 (&)。这些函数使用内置的 C 运算符对所有数组元素执行指定的运算符。

这些实用函数的特定示例修改了所提供的一组位标志。但是,如果这是一个问题,您可以修改函数以接受三个参数,一个用于操作的结果,另外两个用于操作中使用的两组位标志。

void AndBits(BitSet s1, const BitSet s2)
{
    size_t nLen = sizeof(BitSet);

    for (; nLen > 0; nLen--) {
        *s1++ &= *s2++;
    }
}

void OrBits(BitSet s1, const BitSet s2)
{
    size_t nLen = sizeof(BitSet);

    for (; nLen > 0; nLen--) {
        *s1++ |= *s2++;
    }
}

void XorBits(BitSet s1, const BitSet s2)
{
    size_t nLen = sizeof(BitSet);

    for (; nLen > 0; nLen--) {
        *s1++ ^= *s2++;
    }
}

如果您使用这种方法需要不止一种大小的位标志类型,那么最灵活的方法是消除 typedef 并直接使用各种大小的 unsigned char 数组。此更改将需要修改实用函数的接口,将BitSet 替换为unsigned char 指针和unsigned char 数组,其中定义了位标志变量。除了unsigned char 指针,您还需要指定数组的长度。

您也可以考虑采用类似于 Is concatenating arbitrary number of strings with nested function calls in C undefined behavior? 中对文本字符串所做的方法。

【讨论】:

    【解决方案3】:

    与其尝试将大得离谱的数字分配给枚举以便您可以拥有数百位宽的位域,不如让编译器为您的标志名称分配一个正常的从零开始的数字序列,并模拟一个宽位域使用无符号字符数组。您可以使用 unsigned char bits[128] 获得 1024 位位域,并编写 get_flag()set_flag() 访问器函数来屏蔽所涉及的少量额外工作。

    但是,更好的建议是再次查看您的设计,并问自己“为什么我需要超过一百个不同的标志?”。在我看来,您真正需要的是重新设计。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-05-17
      • 2015-01-18
      • 2017-12-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多