在C中的位字段操作

在C中testing和设置一个整数中的个别位的经典问题可能是最常见的中级编程技能之一。 您可以使用简单的位掩码来设置和testing

unsigned int mask = 1<<11; if (value & mask) {....} // Test for the bit value |= mask; // set the bit value &= ~mask; // clear the bit 

一个有趣的博客文章认为,这是错误的倾向性,难以维护和不良的做法。 C语言本身提供了types安全和便携的位级访问:

 typedef unsigned int boolean_t; #define FALSE 0 #define TRUE !FALSE typedef union { struct { boolean_t user:1; boolean_t zero:1; boolean_t force:1; int :28; /* unused */ boolean_t compat:1; /* bit 31 */ }; int raw; } flags_t; int create_object(flags_t flags) { boolean_t is_compat = flags.compat; if (is_compat) flags.force = FALSE; if (flags.force) { [...] } [...] } 

但是这让我感到害怕

我和我的同事有关这个有趣的说法仍然没有解决。 两种风格都起作用,而我保持经典的位掩码方法简单,安全,清晰。 我的同事认为这是普遍和容易的,但bitfield联合方法值得额外的几行,使其便携和更安全。

双方是否有更多的争论? 特别是有一些可能的失败,或许是随着sorting,位掩码方法可能会错过,但结构方法是安全的?

位字段不像你想象的那样便携,因为“C不能保证机器字中字段的sorting”( The C book )

忽略这一点, 正确使用,任何一种方法都是安全的。 这两种方法都允许符号访问积分variables。 你可以争辩说,位域方法更容易编写,但也意味着更多的代码来审查。

如果问题是设置和清除位是容易出错的,那么正确的做法是编写函数或macros来确保正确执行。

 // off the top of my head #define SET_BIT(val, bitIndex) val |= (1 << bitIndex) #define CLEAR_BIT(val, bitIndex) val &= ~(1 << bitIndex) #define TOGGLE_BIT(val, bitIndex) val ^= (1 << bitIndex) #define BIT_IS_SET(val, bitIndex) (val & (1 << bitIndex)) 

如果你不介意val除了BIT_IS_SET之外必须是一个左值,这使得你的代码可读。 如果这不能使你快乐,那么你把它赋值,加上括号,并用val = SET_BIT(val,someIndex); 这将是等同的。

真的,答案是考虑将你想要的与你想要做的分开。

位字段非常好,易于阅读,但不幸的是,C语言没有指定位域在内存中的布局 ,这意味着它们对于处理磁盘格式或二进制线协议中的打包数据本质上是无用的。 如果你问我,这个决定是C-Ritchie本可以挑选一个订单的一个devise错误,并且坚持下去。

你必须从作家的angular度思考这个问题 – 了解你的观众。 所以有几个“观众”要考虑。

首先是经典的C程序员,他们一辈子掩饰了自己,并且可以在睡梦中做到这一点。

其次是新手,谁也不知道这些东西是什么。 他们在上一份工作中编程php,现在他们为你工作。 (我说这是一个新手谁做的PHP)

如果你写作是为了满足第一个观众(这是全天候的位掩码),你会让他们非常高兴,他们将能够保持代码被蒙上眼睛。 然而,newb可能需要克服大量的学习曲线才能维护你的代码。 他们将需要学习二元运算符,你如何使用这些操作来设置/清除比特等等。你几乎肯定会有newb引入的错误,因为他/她所需的所有技巧都是为了使其工作。

另一方面,如果你写作是为了满足第二个读者,那么新手会更容易维护代码。 他们会有更容易的时间

  flags.force = 0; 

  flags &= 0xFFFFFFFE; 

而第一个听众会变得脾气暴躁,但很难想象他们不能够维持新的语法。 这很难搞砸了。 不会有新的bug,因为newb会更容易维护代码。 你只会听到关于如何“回到我的一天,你需要一个稳定的手和一个磁针镶嵌位……我们甚至没有掩饰! (谢谢XKCD )。

所以我强烈build议使用位掩码的字段来保护你的代码。

根据ANSI C标准,联合使用具有未定义的行为,因此不应使用(或者至less不能被认为是可移植的)。

从ISO / IEC 9899:1999(C99)标准 :

附件J – 可移植性问题:

1以下是未指定的:

– 在结构或联合中存储值时填充字节的值(6.2.6.1)。

– 存储在(6.2.6.1)中的最后一个工会成员以外的工会成员的价值。

6.2.6.1 – 语言概念 – types的表示 – 一般:

6当一个值存储在一个结构或联合types的对象中,包括成员对象中时,与任何填充字节相对应的对象表示的字节都是未指定的值[42])结构或联合对象的值是从来没有陷阱表示,即使结构或联合对象的成员的值可能是陷阱表示。

7如果值存储在uniontypes的对象的成员中,则与该成员不相对应但与其他成员相对应的对象表示的字节将采用未指定的值。

所以,如果你想保持位域↔整数对应,并保持可移植性,我强烈build议你使用位掩码方法,这与链接的博客文章相反,这不是不好的做法。

bitfield方法是什么让你畏缩?

两种技术都有自己的位置,唯一的决定是使用哪一种:

对于简单的“一次性”位摆弄,我直接使用按位运算符。

对于任何更复杂的事情,比如硬件寄存器映射,位域方法都会胜手。

  • 位字段的使用更为简洁(以写/略/更加详细为代价)。
  • 比特字段更加健壮(无论如何,什么大小是“int”)
  • 位域通常和位运算符一样快。
  • 当您混合使用单个位和多个位字段时,位字段非常强大,并且提取多位字段涉及手动位移的加载。
  • 位字段是有效的自我logging。 通过定义结构并命名元素,我知道它的意义。
  • 位域也可以无缝地处理大于单个整数的结构。
  • 对于位操作符,典型的(坏)操作是对于位掩码的#定义。

  • 唯一需要注意的地方就是确保编译器真的把对象压缩成你想要的大小。 我不记得这是由标准定义的,所以assert(sizeof(myStruct)== N)是一个有用的检查。

无论哪种方式,GNU软件已经使用了几十年的位域,并没有对它们造成任何伤害。 我喜欢它们作为函数的参数。

我认为位域是常规的 ,而不是结构。 每个人都知道如何和值来设置各种选项,编译器将其归结为在CPU上非常有效的按位操作。

如果您使用正确的方式使用掩码和testing,则编译器提供的抽象应该使其健壮,简单,可读和干净。

当我需要一组开/关开关时,我将继续在C中使用它们。

您提到的博客文章提到了raw联盟领域作为位域的替代访问方法。

目的博客文章的作者使用raw的是好的,但是如果你打算使用它的其他任何东西(例如位字段的序列化,设置/检查个别位),灾难只是在你身边等着你。 内存中的位的顺序依赖于架构,内存填充规则因编译器而异(见维基百科 ),所以每个位字段的确切位置可能不同,换句话说,您永远无法确定每个位字段的raw位对应哪个位。

但是,如果你不打算混合它,你最好采取raw ,你会是安全的。

那么你不能错误的结构映射,因为这两个领域是可访问的,他们可以互换使用。

位域的一个好处是你可以很容易地聚合选项:

 mask = USER|FORCE|ZERO|COMPAT; vs flags.user = true; flags.force = true; flags.zero = true; flags.compat = true; 

在一些处理协议选项的环境中,它可能会很老,不得不单独设置选项,或者使用多个参数来渡轮中间状态来产生最终结果。

但有时设置flag.blah并在您的IDE中popup列表是非常好的,特别是如果您喜欢我,并且不记得您想要设置的标志的名称,而不经常引用列表。

我个人有时候会回避布尔types的声明,因为在某些时候,我会误以为我刚刚切换的字段并不依赖于(看multithreading并发性)其他“看似”的状态,碰巧共享相同的32位字的无关字段。

我的投票是,这取决于情况的背景,在某些情况下,这两种方法可能会很好。

在C ++中,只需使用std::bitset<N>

这是错误倾向,是的。 我在这种代码中看到了很多错误,主要是因为有些人觉得应该把它和业务逻辑搞得乱七八糟,造成维护噩梦。 他们认为“真正的”程序员可以写入value |= mask;value &= ~mask; 甚至在任何地方甚至更糟的事情,这只是好的。 更妙的是,如果有一些增量操作符,几个memcpy ,指针转换,以及当时想到的任何模糊和容易出错的语法。 当然,没有必要保持一致,你可以用两种或三种不同的方式翻转位,随机分配。

我的build议是:

  1. 封装这个—-在一个类中,使用诸如SetBit(...)ClearBit(...) 。 (如果你没有C语言的课程,那么你可以logging所有的行为。
  2. unit testing该类或模块。

你的第一个方法是可取的,恕我直言。 为什么混淆这个问题? 位摆弄是一个非常基本的东西。 C做对了。 Endianess并不重要。 联合解决scheme唯一能做的就是命名东西。 11可能是神秘的,但#被定义为一个有意义的名字或者enum应该就足够了。

不能处理像“|&^〜”这样的基本面的程序员可能是错误的工作。

当我谷歌“C运营商”

前三页是:

http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V40F_HTML/AQTLTBTE/DOCU_059.HTM http://www.cs。 mun.ca/~michael/c/op.html

因此,我认为关于新手语言的争论有点愚蠢。

我几乎总是使用具有位掩码的逻辑操作,直接或作为macros。 例如

  #define ASSERT_GPS_RESET() { P1OUT &= ~GPS_RESET ; } 

顺便说一句,你原来的问题中的联合定义不适用于我的处理器/编译器组合。 inttypes只有16位宽,位域定义是32.为了使它更容易移植,那么你将不得不定义一个新的32位types,然后你可以映射到每个目标体系结构所需的基types,作为移植练习。 在我的情况

 typedef unsigned long int uint32_t 

并在原来的例子

 typedef unsigned int uint32_t typedef union { struct { boolean_t user:1; boolean_t zero:1; boolean_t force:1; int :28; /* unused */ boolean_t compat:1; /* bit 31 */ }; uint32_t raw; } flags_t; 

重叠的int也应该做无符号的。

那么,我想这是做这件事的一种方法,但我总是希望保持简单 。

一旦你习惯了,使用口罩是直截了当,明确和便携。

位字段很简单,但是不需要额外的工作就不能移植。

如果您必须编写符合MISRA的代码,那么MISRA指南对位域,联合以及C的许多其他方面都不满意,以避免未定义的或实现相关的行为。

一般来说,阅读和理解起来更容易,维护也更容易。 如果你有一些C的新手,那么“更安全”的方法可能会让他们更容易理解。

位字段是伟大的,除了位操作操作不是primefaces的,因此可能导致在multithreading应用程序中的问题。

例如,可以假定一个macros:

 #define SET_BIT(val, bitIndex) val |= (1 << bitIndex) 

定义一个primefaces操作,因为| =是一个语句。 但编译器生成的普通代码不会尝试使| =primefaces。

所以,如果多个线程执行不同的设置位操作,其中一个设置位操作可能是虚假的。 由于这两个线程将执行:

  thread 1 thread 2 LOAD field LOAD field OR mask1 OR mask2 STORE field STORE field 

结果可以是字段OR = mask1 OR mask2(intented),或者结果可以是field = field OR mask1(not intented)或者结果可以是field = field OR mask2(不打算)。