C99中为何无法用位域结构体数组实现?联合应用问题咨询
问题原因分析
你用32个单比特位域结构体组成数组失败的核心原因是内存不重叠:
- 单个含1位域的结构体,其大小由位域的宿主类型决定。如果用
unsigned int作为位域类型,哪怕只有1位,结构体的大小也是4字节(C标准规定结构体大小至少能容纳其所有成员,而unsigned int通常占4字节)。32个这样的结构体组成数组,总大小是128字节,远大于int的4字节。 - 当你把这个数组和
int放在联合中时,int的4字节只会覆盖数组第一个结构体的内存空间,剩下31个结构体的内存和int完全不重叠,它们的位域值是未初始化的(通常为0),这就是输入1、2时位输出全0的原因。
可行优化方案
要让位域和int的内存完全重叠,必须保证位域结构体的总大小等于int的大小(4字节)。推荐两种符合限制的思路:
方案1:单个32位域结构体
定义一个包含32个1位域的结构体,宿主类型用unsigned int(刚好4字节),和int放在联合中:
#include <stdio.h> typedef struct { // 注意:位域顺序依赖编译器,GCC默认从低位到高位分配,这里b31对应最高位需调整顺序 unsigned int b31:1, b30:1, b29:1, b28:1, b27:1, b26:1, b25:1, b24:1, b23:1, b22:1, b21:1, b20:1, b19:1, b18:1, b17:1, b16:1, b15:1, b14:1, b13:1, b12:1, b11:1, b10:1, b9:1, b8:1, b7:1, b6:1, b5:1, b4:1, b3:1, b2:1, b1:1, b0:1; } BitFields; union byte_set { int val; unsigned char bytes[4]; BitFields bits; };
显示位时,用宏展开直接访问每个位域(不用数组下标和位运算符):
#define PRINT_BITS(bs) do { \ printf("%u%u%u%u %u%u%u%u ", bs.bits.b31, bs.bits.b30, bs.bits.b29, bs.bits.b28, bs.bits.b27, bs.bits.b26, bs.bits.b25, bs.bits.b24); \ printf("%u%u%u%u %u%u%u%u ", bs.bits.b23, bs.bits.b22, bs.bits.b21, bs.bits.b20, bs.bits.b19, bs.bits.b18, bs.bits.b17, bs.bits.b16); \ printf("%u%u%u%u %u%u%u%u ", bs.bits.b15, bs.bits.b14, bs.bits.b13, bs.bits.b12, bs.bits.b11, bs.bits.b10, bs.bits.b9, bs.bits.b8); \ printf("%u%u%u%u %u%u%u%u\n", bs.bits.b7, bs.bits.b6, bs.bits.b5, bs.bits.b4, bs.bits.b3, bs.bits.b2, bs.bits.b1, bs.bits.b0); \ } while(0) int main() { union byte_set bs; printf("Enter an int: "); scanf("%d", &bs.val); // 显示2个word(这里假设int是4字节,word为2字节) unsigned short *words = (unsigned short*)&bs.val; printf("2 words: 0x%04x 0x%04x\n", words[1], words[0]); // 小端系统下调整顺序 // 显示4个byte printf("4 bytes: 0x%02x 0x%02x 0x%02x 0x%02x\n", bs.bytes[3], bs.bytes[2], bs.bytes[1], bs.bytes[0]); // 显示32位 PRINT_BITS(bs); return 0; }
注意:位域的顺序需要根据编译器调整,比如GCC默认将位域从字节的低位开始分配,若要b31对应int的最高位,可能需要反过来定义位域成员顺序。
方案2:复用字节数组+单字节位域结构体
如果觉得32个位域的结构体太繁琐,可以复用联合里的4字节数组,每个字节对应一个8位域的结构体:
#include <stdio.h> typedef struct { unsigned int b7:1, b6:1, b5:1, b4:1, b3:1, b2:1, b1:1, b0:1; } ByteBits; union byte_set { int val; unsigned char bytes[4]; };
显示位时,把每个字节强制转换为ByteBits*访问位域(不用数组下标读取数组,用指针):
void print_bits(union byte_set bs) { // 小端系统下,从最高字节到最低字节依次是bytes[3]到bytes[0] ByteBits *b3 = (ByteBits*)&bs.bytes[3]; ByteBits *b2 = (ByteBits*)&bs.bytes[2]; ByteBits *b1 = (ByteBits*)&bs.bytes[1]; ByteBits *b0 = (ByteBits*)&bs.bytes[0]; printf("%u%u%u%u %u%u%u%u ", b3->b7, b3->b6, b3->b5, b3->b4, b3->b3, b3->b2, b3->b1, b3->b0); printf("%u%u%u%u %u%u%u%u ", b2->b7, b2->b6, b2->b5, b2->b4, b2->b3, b2->b2, b2->b1, b2->b0); printf("%u%u%u%u %u%u%u%u ", b1->b7, b1->b6, b1->b5, b1->b4, b1->b3, b1->b2, b1->b1, b1->b0); printf("%u%u%u%u %u%u%u%u\n", b0->b7, b0->b6, b0->b5, b0->b4, b0->b3, b0->b2, b0->b1, b0->b0); } int main() { union byte_set bs; printf("Enter an int: "); scanf("%d", &bs.val); // 显示2个word unsigned short *words = (unsigned short*)&bs.val; // 用一个if处理大小端(符合仅用一个if的限制) int is_big_endian = (*(unsigned char*)&bs.val == ((unsigned int)bs.val >> 24)); if (is_big_endian) { printf("2 words: 0x%04x 0x%04x\n", words[0], words[1]); } else { printf("2 words: 0x%04x 0x%04x\n", words[1], words[0]); } // 显示4个byte if (is_big_endian) { printf("4 bytes: 0x%02x 0x%02x 0x%02x 0x%02x\n", bs.bytes[0], bs.bytes[1], bs.bytes[2], bs.bytes[3]); } else { printf("4 bytes: 0x%02x 0x%02x 0x%02x 0x%02x\n", bs.bytes[3], bs.bytes[2], bs.bytes[1], bs.bytes[0]); } // 显示32位 print_bits(bs); return 0; }
这个方案代码更紧凑,还能用一个if处理大小端判断,完全符合题目限制。
注意事项
位域的内存布局是编译器相关的,所以代码的可移植性有限,但在C99标准下,针对目标编译器调整位域或字节顺序后,就能正常工作。
内容的提问来源于stack exchange,提问作者lewando54
相关产品推荐
相关产品推荐

