使用联合体可以访问单个位吗?

18

我正在编写一个C程序。我想要一个变量,既可以像char一样访问,又可以访问特定的位。我在考虑可以使用联合体来实现这个需求...

typedef union 
{
    unsigned char status;
    bit bits[8];
}DeviceStatus;

但编译器不喜欢这样。显然在结构体中不能使用位(bit)。那我该怎么办呢?

6个回答

31

当然可以,但你实际上想要使用结构体来定义位(bit):

typedef union
{
  struct
  {
    unsigned char bit1 : 1;
    unsigned char bit2 : 1;
    unsigned char bit3 : 1;
    unsigned char bit4 : 1;
    unsigned char bit5 : 1;
    unsigned char bit6 : 1;
    unsigned char bit7 : 1;
    unsigned char bit8 : 1;
  }u;
  unsigned char status;
}DeviceStatus;

然后你可以访问 DeviceStatus ds; ,你可以访问 ds.u.bit1。另外,一些编译器实际上允许您在联合体内拥有匿名结构,这样您可以只访问 ds.bit1 如果您省略了类型定义中的“u”。


2
C99 6.7.2.1/10:“实现可以分配任何可寻址的存储单元,足以容纳位域。” 不需要分配仅一个char,因此可能根本不会发生别名。 此外,联合内的匿名结构是标准要求的。 - Potatoswatter
你必须给结构体命名吗? - jjxtra
@jixtra:不,你不需要。 - neuviemeporte

4
您有几种可能性。其中一种是只使用布尔数学来获取位:

您有几种可能性。其中之一是仅使用布尔数学来获取位:

int bit0 = 1;
int bit1 = 2;
int bit2 = 4;
int bit3 = 8;
int bit4 = 16;
int bit5 = 32;
int bit6 = 64;
int bit7 = 128;

if (status & bit1)
    // whatever...

另一种方法是使用位域:

struct bits { 
   unsigned bit0 : 1;
   unsigned bit1 : 1;
   unsigned bit2 : 1;
// ...
};

typedef union {
    unsigned char status;
    struct bits bits;
} status_byte;

some_status_byte.status = whatever;
if (status_byte.bits.bit2)
    // whatever...

第一个方法(至少可以争辩说)更具可移植性,但是当您处理状态位时,很有可能代码根本不具备可移植性,因此您可能并不太关心...

“unsigned” 不是比 “char” 更大的数据类型吗?这会导致 sizeof(char) < sizeof(DeviceStatus),可能会引起一些问题。 - torak
1
@Jerry Coffin:只有您的第一个版本保证使用状态位。在union中,各个bitX字段可能会按照编译器的要求进行填充。 - Jens Gustedt
@torak:是的,它可以。如果这对你来说是个问题,那么你可能想把所有字段都改成“unsigned char”。 - Jerry Coffin
@Jens:是的,这就是我说第一个“可能更具可移植性”的原因。另一方面,第二个可以与足够多的编译器一起使用,所以仍然值得一提(至少在我看来是这样)。 - Jerry Coffin
@torak:unsigned 的大小是无关紧要的,因为该类型用于位域;位域必须声明为 int(可选带符号)(或在 C99 中使用 _Bool)。但编译器可能会决定 struct bits 占用多个字节,实际上,使用 gcc,sizeof(struct bits) = sizeof(int) - Gilles 'SO- stop being evil'
1
@torak,请注意 : 1,这意味着该值仅使用一个位来存储。它将类型限制为该位数,只要您不使用大于所使用类型大小的数字,就可以正常使用。 - Jon Hanna

3
typedef union
{
  unsigned char status;
  struct bitFields
  {
    _Bool bit0 : 1;
    _Bool bit1 : 1;
    _Bool bit2 : 1;
    _Bool bit3 : 1;
    _Bool bit4 : 1;
    _Bool bit5 : 1;
    _Bool bit6 : 1;
    _Bool bit7 : 1;
  } bits;
}DeviceStatus;

C99规范是否定义了_Bool类型的大小?我大致查看了一下,没有看到明显的说明。 - torak
按照定义,任何类型至少与 char 一样宽。但这不是重点,因为这些是位域。编译器实际上可以将字段 bitX 放置在状态位上,但没有保证。 - Jens Gustedt
只要具有至少相同数量的位,就可以使用任何_Bool的整数类型作为位域的类型。由于此处使用的大小为1位,因此没有不允许使用的位域类型。 - Jon Hanna

3

正如已经提到的,C语言不能处理比一个字节更小的内存。我会写一个宏:

#define BIT(n) (1 << n)

使用它来访问位。这样,您的访问是相同的,无论您访问的结构的大小如何。您可以编写以下代码:

if (status & BIT(1)) {
   // Do something if bit 1 is set
} elseif (~status | BIT(2) {
   // Do something else if bit 2 is cleared
} else  {
   // Set bits 1 and 2
   status |= BIT(1) | BIT(2)
   // Clear bits 0 and 4
   status &= ~(BIT(0) | BIT(4))
   // Toggle bit 5 
   status ^= BIT(5)
}

这将使您接近您提出的系统,该系统将使用[]而不是()。

0
在C语言中,可寻址的最小单元始终是字节(称为char)。您无法直接访问位。访问位的最接近方法是定义一个名为bitpointer的数据类型,并为其定义一些函数或宏:
#include <stdbool.h>

typedef struct bitpointer {
    unsigned char *pb; /* pointer to the byte */
    unsigned int bit; /* bit number inside the byte */
} bitpointer;

static inline bool bitpointer_isset(const bitpointer *bp) {
    return (bp->pb & (1 << bp->bit)) != 0;
}

static inline void bitpointer_set(const bitpointer *bp, bool value) {
    unsigned char shifted = (value ? 1 : 0) << bp->bit;
    unsigned char cleared = *bp->pb &~ (1 << bp->bit);
    *(bp->pb) = cleared | shifted;
}

我建议不要使用联合体,因为它们的填充顺序是实现定义的,可能是从最高有效位到最低有效位或者从最低有效位到最高有效位(参见ISO C99,6.7.2.1p10)。


0
你可以通过将位放入联合体内的结构中来实现,但这取决于你的实现方式,可能会起作用,也可能不会。语言定义没有指定单独的位将与unsigned char的位匹配的顺序;更糟糕的是,它甚至不能保证位将与unsigned char重叠(编译器可能决定将单独的位放置在字的最高有效位或最低有效位的一侧,而将unsigned char放置在另一侧)。
在你的情况下,通常的技术是使用位运算。定义以位含义命名的常量,例如:
#define FLAG_BUSY 0x01
#define FLAG_DATA_AVAILABLE 0x02
#define FLAG_TRANSMISSION_IN_PROGRESS 0x04
...
#define FLAG_ERROR 0x80

然后是读写单独的位:

if (status & FLAG_BUSY) ... /* test if the device is busy */
status &= ~FLAG_ERROR; /* turn off error flag */
status |= FLAG_TRANSMISSION_IN_PROGRESS /* turn on transmission-in-progress flag */

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接