C阅读二进制数据到结构中

h6my8fg2  于 12个月前  发布在  其他
关注(0)|答案(3)|浏览(99)

我有一个结构

typedef struct {
    uint8_t type;  // 1B -> 1B
    uint16_t hash; // 2B -> 3B
    uint16_t id;   // 2B -> 5B
    uint32_t ip;   // 4B -> 9B
    uint16_t port; // 2B -> 11B
} Data;

字符串
和一些二进制数据(这是磁盘上数据的存储示例)

const unsigned char blob[11] = { 0x00, 0x00, 0x7b, 0x00, 0xea, 0x00, 0x00, 0x00, 0x59, 0x01, 0x00 };


我想把blob“读”到我的结构体中,第一个字节0x00对应于type,第二个和第三个字节0x00, 0x7b对应于hash,等等。
我不能只做Data *data = (Data *)blob,因为Data的实际大小可能会比11大(更快的RAM访问或其他东西。这里不相关。)重点是sizeof(Data) == 16,RAM中的表示可能与磁盘上的压缩表示不同。
那么,我如何才能将我的blob“导入”到一个Data结构体中,而不必为每个属性使用memcpy呢?

k75qkfdt

k75qkfdt1#

点是sizeof(Data) == 16,RAM中的表示可能与磁盘上的压缩表示不同。
由于不能依赖文件中的数据布局来匹配内存中结构的数据布局,因此标准C不提供逐个成员工作的替代方法。
但是从磁盘阅读数据可能与从数组中阅读数据是不同的问题。我想你想象阅读一个或多个完整的原始记录到内存中,然后以某种方式从那里复制,但是如果你可以依靠结构和磁盘之间匹配的各个字段的大小和字节序,那么你可以考虑这个:

Data item;

if (fread(&item.type, sizeof(item.type), 1, input_file) == 0) handle_error();
if (fread(&item.hash, sizeof(item.hash), 1, input_file) == 0) handle_error();
if (fread(&item.ip,   sizeof(item.ip),   1, input_file) == 0) handle_error();
if (fread(&item.id,   sizeof(item.id),   1, input_file) == 0) handle_error();
if (fread(&item.port, sizeof(item.port), 1, input_file) == 0) handle_error();

字符串
这让流处理缓冲(它会处理缓冲,除非你禁用它),减轻你对字节数的计数,并且非常清楚。五次调用fread()可能比五次调用memcpy()稍微贵一点,但是你不太可能注意到打开文件和从中传输数据的成本的差异。
但是,如果您确实需要从包含文件原始字节的内存数组中填充结构,那么每个成员memcpy()是最可移植的方法,而且可能比您想象的更有效。

velaa5lx

velaa5lx2#

假设blob数组中的右侧字节对应于比左侧字节更高的权重,那么,一个简单的解决方案将是按以下方式使用按位运算符:

void importFromBlob(const unsigned char *blob, Data *data) {
    // Import type
    data->type = blob[0];

    // Import hash 
    data->hash = (blob[1] << 8) | blob[2];  //assembled as 0x007b in your exp

    // Import id 
    data->id = (blob[3] << 8) | blob[4];

    // Import ip 
    data->ip = (uint32_t)(blob[5]) | (uint32_t)(blob[6] << 8) | (uint32_t)(blob[7] << 16) | (uint32_t)(blob[8] << 24);

    // Import port
    data->port = (blob[9] << 8) | blob[10];
}

字符串

mdfafbf1

mdfafbf13#

避免这种特殊结构的多次读取或字节复制的最简单方法是显式地用3个初始字节和2个尾随字节填充结构:

typedef struct {
    uint8_t pad0[3];  // 3 bytes at offset 0, unused
    uint8_t type;     // 1 byte  at offset 3
    uint16_t hash;    // 2 bytes at offset 4
    uint16_t id;      // 2 bytes at offset 6
    uint32_t ip;      // 4 bytes at offset 8
    uint16_t port;    // 2 bytes at offset 12
    uint8_t pad1[2];  // 2 bytes at offset 14, unused. total: 16 bytes
} Data;

字符串
你可以用一个fread读取数据:

Data mydata;
    if (fread(&mydata.type, 1, 11, fp) == 11) {
        // mydata was read successfully
        // fields can be used directly assuming correct endianness.
    } else {
        // read error
    }


从内存blob调用也是对memcpy的单个调用:

const unsigned char blob[11] = {
        0x00, 0x00, 0x7b, 0x00, 0xea, 0x00, 0x00, 0x00, 0x59, 0x01, 0x00
    };
    memcpy(&mydata.type, bloc, 11);


阅读二进制数据需要以二进制模式打开文件"rb""wb".
在单个fwrite中写入数据是通过

if (fwrite(&mydata.type, 1, 11, fp) == 11) ...


这个技巧适用于中的结构,但在其他情况下可能不起作用:

  • 如果文件中的字节序与CPU不同,
  • 如果大于一个字节的项的序列是不利的。

因此,在一般情况下,您可能必须使用memcpy将面向紧凑字节的表示的块复制到内存中使用的结构中,调整潜在的字节顺序差异。具有小的固定大小的memcpy通常会有效地进行内联扩展,生成很少的指令,这可以在使用Godbolt Compiler Explorer时进行验证。

相关问题