如今在嵌入式软件开发领域,主要分为两大平台:单片机和嵌入式 Linux。相对来说,单片机的硬件资源(尤其是内存)通常更为有限。当然,现在许多单片机的主频很高,内存也很大,资源相当丰富,那我们还需要考虑资源受限的问题吗?
答案是需要的。在产品成本面前,没有最低,只有更低。一旦技术壁垒被打破,竞争的核心就是成本。许多公司的产品功能可以与主流厂商做到一模一样,但就是因为成本降不下来,导致报价偏高,经常在竞标中失利。
在单片机开发中,内存是极其宝贵的资源。一些8位单片机可能只有几KB的RAM(Flash有时也较少,但通常不像RAM那样稀缺)。如何高效利用每一个 bit,都是工程师们必须面对的挑战。
今天,我就和大家分享一个实用的位数组(Bit Array)技巧。它可以帮助你在单片机这样的资源受限环境中,以更紧凑的方式来存储和操作大量的布尔值。这对于管理设备状态、标志位等场景非常有用。
什么是 Bit 数组?
简单来说,就是定义一个数组,并通过封装好的接口来便捷地操作数组中每一个 bit 的状态。很多朋友在项目中也是这么用的,但可能没有进行系统性的封装。整理一下,以后用起来会更方便。
1. 数据结构定义
首先,我们定义一个头文件 bitarray.h,用于声明数据结构和函数。
#ifndef BIT_ARRAY_H
#define BIT_ARRAY_H
#include <stdint.h>
#include <stdbool.h>
// 位数组结构体
typedef struct {
uint8_t *data; // 存储数据的字节数组
uint32_t bit_count; // 总位数
uint32_t byte_size; // 需要的字节数
} BitArray;
// 错误码
typedef enum {
BITARRAY_OK = 0,
BITARRAY_ERROR_INVALID_INDEX,
BITARRAY_ERROR_NULL_POINTER,
BITARRAY_ERROR_INVALID_SIZE
} BitArrayError;
#endif
这里的 *data 是一个指针,指向一段连续的内存,通常由后面的初始化函数传入的 buffer 数组来提供。bit_count 和 byte_size 则标识了 *data 所指向内存区域的大小。BitArrayError 枚举用于一些防御性编程,方便错误处理。
2. 基础操作宏
在实现具体函数之前,我们定义一些基础的宏,这样后续的代码会清晰易懂,避免晦涩的位运算直接出现在函数逻辑中。这些宏都是经典操作,基于 1 个 byte 对应 8 个 bit 来处理。
// 计算存储指定位数所需的字节数
#define BITARRAY_BYTES_NEEDED(bits) (((bits) + 7) / 8)
// 获取位索引所在的字节位置
#define BITARRAY_BYTE_INDEX(bit_index) ((bit_index) >> 3)
// 获取位在字节内的位置(0-7)
#define BITARRAY_BIT_POS(bit_index) ((bit_index) & 0x07)
// 创建位掩码
#define BITARRAY_BIT_MASK(bit_index) (1U << BITARRAY_BIT_POS(bit_index))
3. 核心功能实现
下面是一个相对完整的 [C语言] 实现,代码中包含详细的注释。你可以根据自己项目的具体需求进行调整和优化,例如注意特定平台的内存管理大小端问题,或者增加线程安全保护等。
// bitarray.c
#include "bitarray.h"
/**
* @brief 初始化位数组
* @param array 位数组指针
* @param buffer 存储缓冲区
* @param buffer_size 缓冲区大小(字节)
* @param bit_count 需要的总位数
* @return 初始化结果
*/
BitArrayError bitarray_init(BitArray *array, uint8_t *buffer,
uint32_t buffer_size, uint32_t bit_count) {
if (!array || !buffer) {
return BITARRAY_ERROR_NULL_POINTER;
}
uint32_t required_bytes = BITARRAY_BYTES_NEEDED(bit_count);
if (buffer_size < required_bytes) {
return BITARRAY_ERROR_INVALID_SIZE;
}
if (bit_count == 0) {
return BITARRAY_ERROR_INVALID_SIZE;
}
array->data = buffer;
array->bit_count = bit_count;
array->byte_size = required_bytes;
// 清零所有位
bitarray_clear_all(array);
return BITARRAY_OK;
}
/**
* @brief 设置指定位为1
* @param array 位数组指针
* @param bit_index 位索引(0 ~ bit_count-1)
* @return 操作结果
*/
BitArrayError bitarray_set(BitArray *array, uint32_t bit_index) {
if (!array) return BITARRAY_ERROR_NULL_POINTER;
if (bit_index >= array->bit_count) return BITARRAY_ERROR_INVALID_INDEX;
uint32_t byte_idx = BITARRAY_BYTE_INDEX(bit_index);
uint8_t bit_mask = BITARRAY_BIT_MASK(bit_index);
array->data[byte_idx] |= bit_mask;
return BITARRAY_OK;
}
/**
* @brief 清除指定位(设为0)
* @param array 位数组指针
* @param bit_index 位索引
* @return 操作结果
*/
BitArrayError bitarray_clear(BitArray *array, uint32_t bit_index) {
if (!array) return BITARRAY_ERROR_NULL_POINTER;
if (bit_index >= array->bit_count) return BITARRAY_ERROR_INVALID_INDEX;
uint32_t byte_idx = BITARRAY_BYTE_INDEX(bit_index);
uint8_t bit_mask = BITARRAY_BIT_MASK(bit_index);
array->data[byte_idx] &= ~bit_mask;
return BITARRAY_OK;
}
/**
* @brief 获取指定位的值
* @param array 位数组指针
* @param bit_index 位索引
* @param value 存储位值的指针
* @return 操作结果
*/
BitArrayError bitarray_get(const BitArray *array, uint32_t bit_index, bool *value) {
if (!array || !value) return BITARRAY_ERROR_NULL_POINTER;
if (bit_index >= array->bit_count) return BITARRAY_ERROR_INVALID_INDEX;
uint32_t byte_idx = BITARRAY_BYTE_INDEX(bit_index);
uint8_t bit_mask = BITARRAY_BIT_MASK(bit_index);
*value = (array->data[byte_idx] & bit_mask) != 0;
return BITARRAY_OK;
}
/**
* @brief 切换指定位(0变1,1变0)
* @param array 位数组指针
* @param bit_index 位索引
* @return 操作结果
*/
BitArrayError bitarray_toggle(BitArray *array, uint32_t bit_index) {
if (!array) return BITARRAY_ERROR_NULL_POINTER;
if (bit_index >= array->bit_count) return BITARRAY_ERROR_INVALID_INDEX;
uint32_t byte_idx = BITARRAY_BYTE_INDEX(bit_index);
uint8_t bit_mask = BITARRAY_BIT_MASK(bit_index);
array->data[byte_idx] ^= bit_mask;
return BITARRAY_OK;
}
/**
* @brief 设置所有位为1
* @param array 位数组指针
* @return 操作结果
*/
BitArrayError bitarray_set_all(BitArray *array) {
if (!array) return BITARRAY_ERROR_NULL_POINTER;
for (uint32_t i = 0; i < array->byte_size; i++) {
array->data[i] = 0xFF;
}
// 清除多余的位(如果总位数不是8的倍数)
uint32_t extra_bits = array->bit_count & 0x07;
if (extra_bits > 0) {
uint8_t mask = (1U << extra_bits) - 1;
array->data[array->byte_size - 1] &= mask;
}
return BITARRAY_OK;
}
/**
* @brief 清除所有位为0
* @param array 位数组指针
* @return 操作结果
*/
BitArrayError bitarray_clear_all(BitArray *array) {
if (!array) return BITARRAY_ERROR_NULL_POINTER;
for (uint32_t i = 0; i < array->byte_size; i++) {
array->data[i] = 0x00;
}
return BITARRAY_OK;
}
/**
* @brief 检查所有位是否都为0
* @param array 位数组指针
* @param is_empty 存储结果的指针
* @return 操作结果
*/
BitArrayError bitarray_is_empty(const BitArray *array, bool *is_empty) {
if (!array || !is_empty) return BITARRAY_ERROR_NULL_POINTER;
for (uint32_t i = 0; i < array->byte_size; i++) {
if (array->data[i] != 0) {
*is_empty = false;
return BITARRAY_OK;
}
}
*is_empty = true;
return BITARRAY_OK;
}
小结与思考
这套 BitArray 的实现提供了一种在资源受限的单片机系统中高效管理布尔状态的方法。通过位操作,我们可以将8个布尔值压缩在1个字节中,相比直接用 bool 数组(通常1个 bool 占1个字节),可以节省多达 87.5% 的 RAM 空间。
在实际的 [单片机开发] 项目中,当你有大量的设备状态标志、错误码集合或者需要记录大量传感器阈值状态时,这个小技巧就能派上大用场。它不仅节约了宝贵的内存,其封装好的接口也让代码更清晰、更易于维护。
当然,技术方案没有银弹。使用位数组的代价是牺牲了一点访问速度(因为需要计算字节和掩码),并且在计算机基础层面要求开发者对位运算有清晰的理解。但在绝大多数对内存敏感的单片机应用里,这种空间换时间(或者说,用一点点时间换大量空间)的 trade-off 是非常值得的。
希望这个分享能给你带来启发。如果你有更好的实现思路或者应用场景,欢迎在 云栈社区 与其他开发者一起交流探讨。