前置知识: C

位域

2 minIntermediate2026/6/14

位域与紧凑数据存储

概述

位域(bit-field)是C语言中一种特殊的结构体成员,允许按位指定成员占用的位数。位域用于在内存受限的场景中紧凑存储数据,如网络协议头部、硬件寄存器映射和标志位集合。理解位域的用法和限制,对于编写高效的数据结构至关重要。

基础概念

位域的声明

struct {
    类型 成员名 : 宽度;  // 宽度是该成员占用的位数
};

位域的用途

  • 节省内存:多个小范围值共享一个存储单元
  • 硬件寄存器映射:精确匹配寄存器的位布局
  • 协议头部定义:精确控制网络包的位布局

位域的限制

  • 不能对位域成员取地址(&
  • 位域成员不能是数组
  • 位域的布局依赖编译器实现
  • 位域的符号性取决于基础

快速上手

基本位域

#include <stdio.h>

// 不使用位域:每个标志占4字节
struct FlagsNormal {
    int is_red;     // 4字节
    int is_green;   // 4字节
    int is_blue;    // 4字节
    int is_alpha;   // 4字节
}; // 总共16字节

// 使用位域:每个标志占1位
struct FlagsBitfield {
    unsigned int is_red : 1;    // 1位
    unsigned int is_green : 1;  // 1位
    unsigned int is_blue : 1;   // 1位
    unsigned int is_alpha : 1;  // 1位
}; // 总共4字节(一个unsigned int),实际只用了4位

int main(void) {
    struct FlagsBitfield flags = {0};
    flags.is_red = 1;
    flags.is_green = 0;
    flags.is_blue = 1;
    flags.is_alpha = 1;

    printf("大小: %zu 字节\n", sizeof(struct FlagsBitfield)); // 通常4
    printf("红: %d, 绿: %d, 蓝: %d, 透明: %d\n",
           flags.is_red, flags.is_green, flags.is_blue, flags.is_alpha);

    return 0;
}

不同宽度的位域

#include <stdio.h>

struct PackedData {
    unsigned int type : 3;      // 3位,值范围0-7
    unsigned int priority : 2;  // 2位,值范围0-3
    unsigned int flags : 4;     // 4位,值范围0-15
    unsigned int id : 10;       // 10位,值范围0-1023
}; // 总共19位,通常占4字节

int main(void) {
    struct PackedData data = {0};
    data.type = 5;        // 0-7
    data.priority = 3;    // 0-3
    data.flags = 12;      // 0-15
    data.id = 999;        // 0-1023

    printf("大小: %zu 字节\n", sizeof(struct PackedData));
    printf("type=%d, priority=%d, flags=%d, id=%d\n",
           data.type, data.priority, data.flags, data.id);

    return 0;
}

详细用法

位域的存储单元

位域成员按顺序排列,当当前存储单元空间不足时,编译器可能分配新的存储单元:

#include <stdio.h>

struct Example1 {
    unsigned int a : 5;   // 第1个存储单元:5位
    unsigned int b : 10;  // 第1个存储单元:5+10=15位
    unsigned int c : 20;  // 第1个单元放不下,开始第2个存储单元
}; // 通常8字节(2个unsigned int)

struct Example2 {
    unsigned char a : 3;  // 1字节存储单元
    unsigned char b : 5;  // 同一字节
    unsigned short c : 12; // 新的2字节存储单元
};

int main(void) {
    printf("Example1 大小: %zu\n", sizeof(struct Example1)); // 通常8
    printf("Example2 大小: %zu\n", sizeof(struct Example2)); // 通常4
    return 0;
}

无名位域

无名位域用于填充和对齐控制:

#include <stdio.h>

struct HardwareReg {
    unsigned int enable : 1;    // 第0位
    unsigned int mode : 2;      // 第1-2位
    unsigned int : 5;           // 第3-7位:未使用,填充
    unsigned int speed : 3;     // 第8-10位
    unsigned int : 0;           // 强制对齐到下一个存储单元
    unsigned int data : 16;     // 新的存储单元
};

int main(void) {
    printf("大小: %zu\n", sizeof(struct HardwareReg));
    return 0;
}

有符号位域

位域可以使用有符号型,但最高位是符号位:

#include <stdio.h>

struct SignedBitfield {
    signed int value : 4;  // 4位有符号数,范围-8到7
};

int main(void) {
    struct SignedBitfield s;

    s.value = 7;   // 最大正值
    printf("value = %d\n", s.value); // 7

    s.value = -8;  // 最小负值
    printf("value = %d\n", s.value); // -8

    s.value = 8;   // 溢出!
    printf("value = %d\n", s.value); // 实现定义,通常为-8

    return 0;
}

位域与枚举结合

#include <stdio.h>

typedef enum {
    COLOR_RED,
    COLOR_GREEN,
    COLOR_BLUE,
    COLOR_ALPHA
} ColorChannel;

// 使用位域存储颜色
typedef struct {
    unsigned int channels : 4;  // 4位,最多16个通道
    unsigned int depth : 6;     // 6位,最大63位深度
    unsigned int has_alpha : 1; // 1位标志
} ColorFormat;

const char *format_name(ColorFormat fmt) {
    if (fmt.channels == 3 && fmt.has_alpha == 0) return "RGB";
    if (fmt.channels == 4 && fmt.has_alpha == 1) return "RGBA";
    return "未知格式";
}

int main(void) {
    ColorFormat rgb = { .channels = 3, .depth = 8, .has_alpha = 0 };
    ColorFormat rgba = { .channels = 4, .depth = 16, .has_alpha = 1 };

    printf("RGB: %s, 深度=%d\n", format_name(rgb), rgb.depth);
    printf("RGBA: %s, 深度=%d\n", format_name(rgba), rgba.depth);
    return 0;
}

常见场景

场景一:TCP头部定义

#include <stdio.h>
#include <stdint.h>

// TCP标志位使用位域定义
struct TCPFlags {
    uint16_t fin : 1;     // 结束连接
    uint16_t syn : 1;     // 同步序列号
    uint16_t rst : 1;     // 重置连接
    uint16_t psh : 1;     // 推送功能
    uint16_t ack : 1;     // 确认号有效
    uint16_t urg : 1;     // 紧急指针有效
    uint16_t ece : 1;     // ECN回显
    uint16_t cwr : 1;     // 拥塞窗口减少
};

void print_flags(struct TCPFlags flags) {
    printf("FIN=%d SYN=%d RST=%d PSH=%d ACK=%d URG=%d\n",
           flags.fin, flags.syn, flags.rst,
           flags.psh, flags.ack, flags.urg);
}

int main(void) {
    struct TCPFlags syn_ack = { .syn = 1, .ack = 1 };
    print_flags(syn_ack);
    return 0;
}

场景二:权限标志

#include <stdio.h>

// 文件权限位域
typedef struct {
    unsigned int owner_read : 1;
    unsigned int owner_write : 1;
    unsigned int owner_exec : 1;
    unsigned int group_read : 1;
    unsigned int group_write : 1;
    unsigned int group_exec : 1;
    unsigned int other_read : 1;
    unsigned int other_write : 1;
    unsigned int other_exec : 1;
} FilePermissions;

void print_permissions(FilePermissions perm) {
    printf("%c", perm.owner_read ? 'r' : '-');
    printf("%c", perm.owner_write ? 'w' : '-');
    printf("%c", perm.owner_exec ? 'x' : '-');
    printf("%c", perm.group_read ? 'r' : '-');
    printf("%c", perm.group_write ? 'w' : '-');
    printf("%c", perm.group_exec ? 'x' : '-');
    printf("%c", perm.other_read ? 'r' : '-');
    printf("%c", perm.other_write ? 'w' : '-');
    printf("%c", perm.other_exec ? 'x' : '-');
    printf("\n");
}

int main(void) {
    // rwxr-xr-x (0755)
    FilePermissions perm755 = {
        .owner_read = 1, .owner_write = 1, .owner_exec = 1,
        .group_read = 1, .group_write = 0, .group_exec = 1,
        .other_read = 1, .other_write = 0, .other_exec = 1
    };

    // rw-r--r-- (0644)
    FilePermissions perm644 = {
        .owner_read = 1, .owner_write = 1, .owner_exec = 0,
        .group_read = 1, .group_write = 0, .group_exec = 0,
        .other_read = 1, .other_write = 0, .other_exec = 0
    };

    print_permissions(perm755); // rwxr-xr-x
    print_permissions(perm644); // rw-r--r--
    return 0;
}

场景三:嵌入式寄存器映射

#include <stdio.h>
#include <stdint.h>

// USART控制寄存器位域映射
typedef struct {
    volatile uint32_t UE : 1;      // 使能
    volatile uint32_t UESM : 1;    // 从停止模式使能
    volatile uint32_t RE : 1;      // 接收使能
    volatile uint32_t TE : 1;      // 发送使能
    volatile uint32_t IDLEIE : 1;  // 空闲中断使能
    volatile uint32_t RXNEIE : 1;  // 接收缓冲区非空中断使能
    volatile uint32_t TCIE : 1;    // 传输完成中断使能
    volatile uint32_t TXEIE : 1;   // 发送缓冲区空中断使能
    volatile uint32_t PEIE : 1;    // 校验错误中断使能
    volatile uint32_t PS : 1;      // 校验选择
    volatile uint32_t PCE : 1;     // 校验控制使能
    volatile uint32_t WAKE : 1;    // 唤醒方法
    volatile uint32_t M0 : 1;      // 字长位0
    volatile uint32_t MME : 1;     // MUTE模式使能
    volatile uint32_t CMIE : 1;    // 字符匹配中断使能
    volatile uint32_t OVER8 : 1;   // 过采样模式
    volatile uint32_t DEDT : 5;    // 驱动器使能去断言时间
    volatile uint32_t DEAT : 5;    // 驱动器使能断言时间
    volatile uint32_t RTOIE : 1;   // 接收超时中断使能
    volatile uint32_t EOBIE : 1;   // 块结束中断使能
    volatile uint32_t M1 : 1;      // 字长位1
} USART_CR1;

// 通过指针直接访问硬件寄存器
#define USART1_CR1 ((volatile USART_CR1 *)0x40011000)

void usart_init(void) {
    volatile USART_CR1 *cr1 = USART1_CR1;
    cr1->UE = 1;   // 使能USART
    cr1->TE = 1;   // 使能发送
    cr1->RE = 1;   // 使能接收
}

注意事项

位域的可移植性

位域的布局(位序、对齐、填充)依赖编译器实现,不同编译器可能产生不同布局:

// 在GCC和MSVC上,位域的排列顺序可能不同
struct Bits {
    unsigned int a : 1;
    unsigned int b : 1;
};
// GCC: a在低位,b在高位
// MSVC: 可能相反

对于需要跨平台或与硬件交互的位域,建议使用手动位操作替代:

// 使用位操作替代位域(可移植)
#define REG_ENABLE_BIT  0
#define REG_MODE_BITS   1
#define REG_MODE_MASK   (0x3 << REG_MODE_BITS)
#define REG_SPEED_BITS  3
#define REG_SPEED_MASK  (0x7 << REG_SPEED_BITS)

// 读取
int mode = (reg >> REG_MODE_BITS) & 0x3;

// 写入
reg = (reg & ~REG_MODE_MASK) | (new_mode << REG_MODE_BITS);

不能取位域的地址

struct Flags { unsigned int bit : 1; };
struct Flags f;

// 错误:不能对位域取地址
// unsigned int *p = &f.bit;  // 编译错误

// 替代方案:使用整体变量
unsigned int *p = &f;  // 取整个结构体的地址

位域溢出

赋值超过位域的表示范围会导致截断:

struct { unsigned int val : 3; } s;  // 范围0-7

s.val = 7;   // 正常
s.val = 8;   // 溢出!实际存储的值是0(8 = 1000b,截断为000b)

进阶用法

位域与联合体配合

#include <stdio.h>
#include <stdint.h>

// 同时支持位域访问和整体访问
typedef union {
    uint32_t value;  // 整体访问
    struct {
        uint32_t enable : 1;
        uint32_t mode : 3;
        uint32_t speed : 3;
        uint32_t : 25;    // 保留位
    } bits;               // 位域访问
} ControlRegister;

int main(void) {
    ControlRegister cr = {0};

    // 通过位域设置
    cr.bits.enable = 1;
    cr.bits.mode = 5;
    cr.bits.speed = 3;

    // 通过整体读取
    printf("寄存器值: 0x%08X\n", cr.value);

    // 通过整体设置
    cr.value = 0x0000002B;
    printf("enable=%d, mode=%d, speed=%d\n",
           cr.bits.enable, cr.bits.mode, cr.bits.speed);

    return 0;
}

位域实现状态码

#include <stdio.h>
#include <stdint.h>

// 紧凑的错误码结构
typedef struct {
    uint32_t module : 6;     // 模块编号,0-63
    uint32_t severity : 2;   // 严重程度,0-3
    uint32_t code : 12;      // 错误码,0-4095
    uint32_t line : 12;      // 行号,0-4095
} ErrorCode;

const char *severity_name(int s) {
    static const char *names[] = {"信息", "警告", "错误", "致命"};
    return names[s];
}

void print_error(ErrorCode err) {
    printf("[模块%d][%s] 错误码%d (行%d)\n",
           err.module, severity_name(err.severity), err.code, err.line);
}

int main(void) {
    ErrorCode err = { .module = 5, .severity = 2, .code = 404, .line = 128 };
    print_error(err);

    printf("错误码大小: %zu 字节\n", sizeof(ErrorCode)); // 4字节
    return 0;
}

跨平台位域封装

#include <stdio.h>
#include <stdint.h>
#include <string.h>

// 使用位操作模拟位域(可移植)
typedef struct {
    uint32_t raw;
} PortableFlags;

// 设置指定位
void pf_set(PortableFlags *pf, int bit) {
    pf->raw |= (1U << bit);
}

// 清除指定位
void pf_clear(PortableFlags *pf, int bit) {
    pf->raw &= ~(1U << bit);
}

// 获取指定位
int pf_get(PortableFlags *pf, int bit) {
    return (pf->raw >> bit) & 1;
}

// 设置多位字段
void pf_set_field(PortableFlags *pf, int start, int width, uint32_t value) {
    uint32_t mask = ((1U << width) - 1) << start;
    pf->raw = (pf->raw & ~mask) | ((value << start) & mask);
}

// 获取多位字段
uint32_t pf_get_field(PortableFlags *pf, int start, int width) {
    return (pf->raw >> start) & ((1U << width) - 1);
}

int main(void) {
    PortableFlags pf = {0};

    pf_set(&pf, 0);   // 设置第0位
    pf_set(&pf, 3);   // 设置第3位
    printf("原始值: 0x%X\n", pf.raw); // 0x9

    pf_set_field(&pf, 4, 4, 0xA); // 设置第4-7位为10
    printf("设置字段后: 0x%X\n", pf.raw); // 0xA9

    printf("字段值: %u\n", pf_get_field(&pf, 4, 4)); // 10

    return 0;
}