#defined比特标志和枚举 - “c”中的和平共存

时间:2009-10-03 23:06:03

标签: c enums macros bitflags

我刚刚发现了bitflags的乐趣。我有几个关于在C语言中使用bitflags的“最佳实践”的问题。我从网上找到的各种例子中学到了一切,但仍然有问题。

为了节省空间,我在结构(A->flag)中使用单个32位整数字段来表示几组不同的布尔属性。总之,20个不同的比特是#define d。其中一些是真正的存在/不存在标志(STORAGE-INTERNAL与STORAGE-EXTERNAL)。其他人具有两个以上的值(例如互斥的格式集:FORMAT-A,FORMAT-B,FORMAT-C)。我已经定义了用于设置特定位的宏(同时关闭互斥位)。如果在标志中设置了特定的位组合,我还定义了用于测试的宏。

然而,在上述方法中丢失的是由枚举最佳捕获的标志的特定分组。对于编写函数,我想使用枚举(例如,STORAGE-TYPE和FORMAT-TYPE),以便函数定义看起来不错。我希望仅使用枚举来传递参数,使用#defined宏来设置和测试标志。

  1. (a)如何以便携方式(跨32位/ 64位平台)将标志(A->flag)定义为32位整数?

  2. (b)我是否应该担心A->flag#define d常量与枚举的存储方式存在差异?

  3. (c)我是否使事情变得不必要地复杂化,这意味着我是否应该坚持使用#define d常量将参数作为普通int传递?在这一切中我还应该担心什么呢?

  4. 我为这个表达不清的问题道歉。它反映了我对潜在问题的无知。

6 个答案:

答案 0 :(得分:7)

有一个C99标题旨在解决确切的问题(a)但由于某种原因,Microsoft没有实现它。幸运的是,you can get <stdint.h> for Microsoft Windows here。每个其他平台都已经拥有它。 32位int类型是uint32_tint32_t。它们也有8,16和64位的味道。

所以,这需要照顾(a)。

(b)和(c)是同一个问题。每当我们开发某些东西时,我们都会做出假设您假设C将可用。您假设可以在某处找到<stdint.h>。您可以始终假设int至少为16位,现在&gt; = 32位假设是相当合理的。

通常,您应该尝试编写不依赖于布局的符合性程序,但是它们会对字长做出假设。您应该担心算法级别的性能,也就是说,我写的是二次,多项式,指数的东西吗?

在(a)您发现性能滞后,以及(b)您已对程序进行了分析之前,您不必担心操作级别的性能。你需要完成工作,而不必担心个别操作。 : - )

哦,我应该补充一点,当您首先在C 中编写程序时,尤其不需要担心低级操作性能。 C是接近金属的快速尽可能快的语言。我们经常在php,python,ruby或者lisp中写东西,因为我们想要一个强大的语言,而且CPU现在如此之快,以至于我们可以使用整个解释器,不用担心一个不完美的位旋转字的选择长度的操作。 : - )

答案 1 :(得分:6)

你可以使用位字段让编译器做一点点琐事。例如:

struct PropertySet {
  unsigned internal_storage : 1;
  unsigned format : 4;
};

int main(void) {
  struct PropertySet x;
  struct PropertySet y[10]; /* array of structures containing bit-fields */
  if (x.internal_storage) x.format |= 2;
  if (y[2].internal_storage) y[2].format |= 2;
  return 0;
}

编辑添加结构数组

答案 2 :(得分:3)

正如其他人所说,使用<stdint.h>以及uint32_tuint_least32_t可以解决您的问题(a)(如果您想担心具有36位字的Burroughs大型机) )。请注意,MSVC不支持C99,但@DigitalRoss显示您可以在哪里获得与MSVC一起使用的合适标头。

你的问题(b)不是问题;如果有必要,C将为您安全地键入转换,但它可能甚至没有必要。

最受关注的领域是(c),特别是格式子领域。在那里,3个值是有效的。您可以通过分配3位并要求3位字段是值1,2或4之一来处理此问题(由于设置的位数太多或太少,任何其他值都无效)。或者您可以分配一个2位数字,并指定0或3(或者,如果您真的想要,1或2中的一个)无效。第一种方法再使用一位(目前不是问题,因为你只使用32位中的20位),但这是一种纯粹的位标记方式。

编写函数调用时,写入没有特别的问题:

some_function(FORMAT_A | STORAGE_INTERNAL, ...);

无论FORMAT_A是#define还是枚举(只要你正确指定枚举值),这都会有效。被叫代码应该检查调用者是否有注意力失效并写道:

some_function(FORMAT_A | FORMAT_B, ...);

但这是一个内部检查模块需要担心,而不是检查模块的用户担心。

如果人们要在flags成员中大量切换位,那么设置和取消设置格式字段的宏可能会有所帮助。有些人可能会争辩说,任何纯粹的布尔字段都不需要它(但我会同情)。最好将flags成员视为不透明,并提供“函数”(或宏)来获取或设置所有字段。人越少出错,就越不会出错。

考虑使用位域是否适合您。我的经验是,它们导致了大代码,而不一定是非常有效的代码; YMMV。

嗯......到目前为止,没有什么是非常明确的。

  • 我会将枚举用于所有内容,因为它们保证在调试器中可见,其中#define值不是。
  • 我可能不会提供获取或设置位的宏,但有时候我是个残酷的人。
  • 我会提供有关如何设置flags字段的格式部分的指导,并可能提供一个宏来执行此操作。

像这样,也许:

enum { ..., FORMAT_A = 0x0010, FORMAT_B = 0x0020, FORMAT_C = 0x0040, ... };
enum { FORMAT_MASK = FORMAT_A | FORMAT_B | FORMAT_C };

#define SET_FORMAT(flag, newval)    (((flag) & ~FORMAT_MASK) | (newval))
#define GET_FORMAT(flag)            ((flag) & FORMAT_MASK)

SET_FORMAT如果准确使用是安全的,但如果被滥用则是可怕的。宏的一个优点是,您可以使用在必要时彻底验证事物的功能替换它们;如果人们一致地使用宏,这种方法很有效。

答案 3 :(得分:2)

对于问题a,如果您正在使用C99(您可能正在使用它),则可以使用uint32_t预定义类型(或者,如果未预定义,则可以在{{1}中找到它头文件)。

答案 4 :(得分:1)

关于(c):如果您的枚举被正确定义,您应该能够将它们作为参数传递而不会出现问题。需要考虑的一些事项:

  1. 枚举存储通常是 编译器具体,所以取决于 你是什​​么样的发展 做(你没有提到它是否是 Windows vs. Linux与嵌入式对比 嵌入式Linux :))你可能想要 访问枚举的编译器选项 存储以确保没有 那里的问题。我普遍同意 以上的共识即 编译器应该投你的 适当的枚举 - 但是 这是值得注意的事情。
  2. 如果你正在做的话 嵌入式工作,静态质量很多 检查PC Lint等程序 如果你开始也会“吠” 聪明的枚举,#define和 位域。如果你在做 需要通过的发展 通过任何质量门,这 可能需要记住一些事情。 实际上,有些汽车标准 (如MISRA-C)彻头彻尾 如果你试图获得触发,那就太烦人了 用bitfields。
  3. “我刚刚发现了喜悦 bitflags。“我同意你的意见 - 我发现了 它们非常有用。

答案 5 :(得分:0)

我在上面的每个答案中添加了评论。我想我有一些清晰度。它似乎更清晰,因为它显示在调试器中并保持字段分开。宏可用于设置和获取值。

我还读过,枚举存储为小整数 - 据我所知,布尔测试不是问题,因为这些将从最右边的位开始进行。但是,枚举可用于存储大整数(1 <&lt; 21)??

再次感谢大家。我已经比两天前做的更多了!

~Russ