如果您关心可移植性(特别是对于强制 16 位和 32 位变量自然对齐的大端架构),您不能只将 struct 的内存布局写入磁盘。编译器的下一个版本可能会以不同的方式打包数据并破坏与所有数据文件的兼容性。不止一家大公司发现,他们通过在另一个 CPU 上编译而不进行规范化,意外创建了两种数据格式,大端和小端。通常,没有任何简单的方法可以判断旧文件保存在哪个文件中。请记住,数据比代码更有效!
这假设你想在你的程序中使用ip_header结构,它应该被填充以便高效访问,并且它的目的不仅仅是隐藏文件布局。
当不同大小的字段散布在一起时,单独设置它们不是一个好方法。您不能假设实现可以使用任意未对齐的地址作为指针。在这种情况下,我也没有假设该文件与您的 CPU 具有相同的字节序;我将字节顺序定义为大端。 (如果您希望此代码在 x86 这样的小端 CPU 上运行,您可以将顺序定义为小端,但仍然使用诸如 glib 之类的库或操作系统。)
您可以将磁盘上的布局移植到内存中的结构,如下所示:
#include <arpa/inet.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct ip_header {
uint8_t version;
uint8_t header_length;
uint8_t service_type;
uint16_t total_length;
uint16_t identification;
uint8_t flags;
uint16_t fragment_offset;
uint8_t ttl;
uint8_t protocol;
uint16_t checksum;
uint32_t src;
uint32_t dest;
/* other fields for options if needed */
} ip_header;
#define IP_HEADER_DISK_LEN 22U
bool read_ip_header( FILE* const input, ip_header* const d )
{
char buffer[IP_HEADER_DISK_LEN];
if ( IP_HEADER_DISK_LEN !=
fread( buffer, 1, IP_HEADER_DISK_LEN, input ) ) {
return false;
}
memset( d, 0, sizeof(*d) );
memcpy( &d->version, &buffer[0], sizeof(d->version) );
memcpy( &d->header_length, &buffer[1], sizeof(d->header_length) );
memcpy( &d->service_type, &buffer[2], sizeof(d->service_type) );
memcpy( &d->total_length, &buffer[3], sizeof(d->total_length) );
d->total_length = ntohs(d->total_length);
memcpy( &d->identification, &buffer[5], sizeof(d->identification) );
d->identification = ntohs(d->identification);
memcpy( &d->flags, &buffer[7], sizeof(d->flags) );
memcpy( &d->fragment_offset, &buffer[8], sizeof(d->fragment_offset) );
d->fragment_offset = ntohs(d->fragment_offset);
memcpy( &d->ttl, &buffer[10], sizeof(d->ttl) );
memcpy( &d->protocol, &buffer[11], sizeof(d->protocol) );
memcpy( &d->checksum, &buffer[12], sizeof(d->checksum) );
d->checksum = ntohs(d->checksum);
memcpy( &d->src, &buffer[14], sizeof(d->src) );
d->src = ntohl(d->src);
memcpy( &d->dest, &buffer[18], sizeof(d->dest) );
d->dest = ntohl(d->dest);
return true;
}
这对整个标头进行单次读取,但您可能会进行单独的 I/O 调用,甚至将文件映射到内存中。大多数现代编译器都足够聪明,可以将连续的memcpy() 调用组合到连续的位置,将不需要的字节交换编译为无操作,并且只编译为不会立即被覆盖的memset() 字节,所以,如果你能得到只需复制字节,这种方式应该同样有效。 (出于您的目的,您甚至可以跳过将填充字节清零并进行字节序转换。)
请记住,读取操作将比处理对齐字节顺序或填充的任何位旋转操作花费的时间要长得多。试图优化这些并不能很好地利用你的时间。特别是如果它在另一个编译器上编译成不兼容的程序!