如何在C++中正确表示FAT12的12位数字数组?
问题描述
我想要在C中实现FAT12规范,其中FAT是一个由12位数字组成的数组。由于C中的数据类型仅支持整字节大小,我尝试在结构体中使用位域来存储两个12位数字,理论上会占用3字节:
struct TwoEntries { uint16_t first : 12; uint16_t second : 12; };
但该结构体的实际大小为4字节,这是由结构体填充导致的,填充会使数组无法适配目标数据。因此我想请教:是否存在正确声明12位数字数组的方法?
解决方案
直接用位域结构体的方式,因编译器填充和对齐规则很难实现跨平台的12位条目数组。更可靠的做法是手动处理字节的打包与解包,或使用编译器特定的无填充属性(注意可移植性),以下是具体方案:
方案1:手动字节操作(推荐,跨平台)
FAT12的存储规则是每3个字节存储两个12位条目:
- 第N个条目(N从0开始):若N为偶数,占用字节
3*(N/2)的全部8位 + 字节3*(N/2)+1的低4位;若N为奇数,占用字节3*(N/2)+1的高4位 + 字节3*(N/2)+2的全部8位。
你可以直接用std::array<uint8_t, ...>或普通字节数组存储FAT数据,再编写读写函数访问指定索引的12位值:
#include <cstdint> #include <vector> // 读取FAT12中第index个12位条目 uint16_t read_fat12_entry(const std::vector<uint8_t>& fat_data, size_t index) { size_t byte_offset = index * 3 / 2; if (index % 2 == 0) { // 偶数索引:低字节全8位 + 下一字节低4位 return (static_cast<uint16_t>(fat_data[byte_offset]) | (static_cast<uint16_t>(fat_data[byte_offset + 1] & 0x0F) << 8)); } else { // 奇数索引:上一字节高4位 + 当前字节全8位 return ((static_cast<uint16_t>(fat_data[byte_offset] & 0xF0) >> 4) | (static_cast<uint16_t>(fat_data[byte_offset + 1]) << 4)); } } // 写入FAT12中第index个12位条目 void write_fat12_entry(std::vector<uint8_t>& fat_data, size_t index, uint16_t value) { // 确保value是12位有效数据 value &= 0x0FFF; size_t byte_offset = index * 3 / 2; if (index % 2 == 0) { // 偶数索引:更新低字节和下一字节的低4位 fat_data[byte_offset] = static_cast<uint8_t>(value & 0x00FF); fat_data[byte_offset + 1] = (fat_data[byte_offset + 1] & 0xF0) | static_cast<uint8_t>((value >> 8) & 0x0F); } else { // 奇数索引:更新当前字节的高4位和下一字节 fat_data[byte_offset] = (fat_data[byte_offset] & 0x0F) | static_cast<uint8_t>((value << 4) & 0xF0); fat_data[byte_offset + 1] = static_cast<uint8_t>((value >> 4) & 0x00FF); } }
这种方式完全避开结构体填充问题,且符合FAT12实际存储格式,跨平台性最好。
方案2:强制结构体无填充(编译器特定)
如果一定要用结构体,可通过编译器扩展禁用填充,但不同编译器语法不同,且位域布局可能存在差异:
GCC/Clang
struct __attribute__((packed)) TwoEntries { uint16_t first : 12; uint16_t second : 12; };
此时结构体大小应为3字节,但需测试确认编译器是否会将两个12位域紧凑排列在3字节中(部分编译器可能仍按uint16_t对齐分配,导致还是4字节)。
MSVC
#pragma pack(push, 1) struct TwoEntries { uint16_t first : 12; uint16_t second : 12; }; #pragma pack(pop)
该方式依赖编译器特性,移植性差,不推荐跨平台项目使用。
方案3:封装成类(更优雅)
把FAT12数组封装成类,内部用字节数组存储,对外提供类似数组的访问接口,比如重载operator[]:
#include <cstdint> #include <vector> class FAT12 { private: std::vector<uint8_t> data; public: explicit FAT12(size_t entry_count) { // 计算所需字节数:每2个条目占3字节 size_t byte_count = (entry_count * 3 + 1) / 2; data.resize(byte_count, 0); } uint16_t operator[](size_t index) const { size_t byte_offset = index * 3 / 2; if (index % 2 == 0) { return (static_cast<uint16_t>(data[byte_offset]) | (static_cast<uint16_t>(data[byte_offset + 1] & 0x0F) << 8)); } else { return ((static_cast<uint16_t>(data[byte_offset] & 0xF0) >> 4) | (static_cast<uint16_t>(data[byte_offset + 1]) << 4)); } } void set_entry(size_t index, uint16_t value) { value &= 0x0FFF; size_t byte_offset = index * 3 / 2; if (index % 2 == 0) { data[byte_offset] = static_cast<uint8_t>(value & 0x00FF); data[byte_offset + 1] = (data[byte_offset + 1] & 0xF0) | static_cast<uint8_t>((value >> 8) & 0x0F); } else { data[byte_offset] = (data[byte_offset] & 0x0F) | static_cast<uint8_t>((value << 4) & 0xF0); data[byte_offset + 1] = static_cast<uint8_t>((value >> 4) & 0x00FF); } } // 暴露底层数据(比如写入磁盘时使用) const std::vector<uint8_t>& get_raw_data() const { return data; } };
这样使用更方便,也隐藏了底层字节操作细节。
内容的提问来源于stack exchange,提问作者wediaklup
相关产品推荐
相关产品推荐

