从 mmap 读取结构
Reading struct from mmap
typedef struct aaa {
int a;
int b;
long ptr_to_st2; //offset from the beginning of the file.
} st1;
typedef struct bbb {
int get;
char it;
} st2;
我有一个使用 mmap
映射到内存的二进制文件。该文件在文件开头包含 st1
,然后是一些数据,然后是 st2
。
unsigned char *filemap; //mmap
st1 *first=(st1 *)filemap;
st2 *second=(st2 *)filemap+first->ptr_to_st2;
printf("%c",second->it);
有人告诉我此代码不正确并且违反了严格的别名规则。
编写此代码的正确方法是什么?
谢谢。
简单来说,int
有对齐要求。假设 sizeof (int)
在您的机器上是两个,我们将您的内存视为一系列块:
[a][a][b][b][c][c][d][d]...
我们可以在 [a]
块中存储一个 int
,在 [b]
块中等等...基本上每隔一个地址...但不能在它们之间。
在我们常见的家用机器上,我们可能实际上能够将它们存储在两者之间,但这是以性能成本为代价的;总线仍然对齐以检索满足对齐要求的整数,因此对于每个未对齐的整数,将通过总线进行两次检索。 这是不受欢迎的。
在不常见的家用机器上(例如旧苹果,甚至那些我们不常编程的东西,例如地球上几乎所有的路由器),这种未对齐的访问将导致类似于段错误的情况,被称为bus error。 这绝对是不受欢迎的!
如果您正确地序列化和反序列化您的信息(而不是仅仅使用类型转换来重新解释部分数组),您将不会遇到任何这些问题。也就是说,逐字节翻译你的结构,例如:
void serialise_st1(void *destination, st1 *source) {
unsigned char *d = destination;
unsigned long s = (unsigned int) source->a;
d[0] = s >> 8;
d[1] = s;
s = (unsigned int) source->b;
d[2] = s >> 8;
d[3] = s;
s = source->ptr_to_st2;
d[4] = s >> 24;
d[5] = s >> 16;
d[6] = s >> 8;
d[7] = s;
}
注意我是如何手动翻译成每个字节的吗?由于需要处理符号,反序列化过程有点困难,但本质上是相反的:我们不是单独分配给每个字节,而是单独 access 每个字节。
void deserialise_st1(st1 *destination, void *source) {
unsigned char *s = source;
*destination = (st1) { .a = (s[0] <= 127 ? s[0] : -(256 - s[0])) * 0x0100
+ s[1],
.b = (s[2] <= 127 ? s[2] : -(256 - s[2])) * 0x0100
+ s[3],
.ptr_to_st2 = (s[4] <= 127 ? s[4] : -(256 - s[4])) * 0x01000000
+ s[5] * 0x00010000
+ s[6] * 0x00000100
+ s[7] };
}
然后,根据您的示例进行调整:
unsigned char *filemap;
st1 first;
deserialise_st1(&first, filemap);
我会把它作为练习留给你写 deserialise_st2
,但如果你这样做有任何问题,请随时询问。
st2 second;
deserialise_st2(&second, filemap + st1.ptr_to_st2);
假设您的代码继续更新 first
或 second
,并且您想将这些更新推送到您的 filemap
,您需要知道它来自的偏移量... 也就是说,您需要将 filemap
关联为指向 first
(first_ptr
) 的指针,并将 filemap + st1.ptr_to_st2
关联为指向 second
的指针( second_ptr
)... 然后:
serialise_st1(first_ptr, &st1);
serialise_st2(second_ptr, &st2);
typedef struct aaa {
int a;
int b;
long ptr_to_st2; //offset from the beginning of the file.
} st1;
typedef struct bbb {
int get;
char it;
} st2;
我有一个使用 mmap
映射到内存的二进制文件。该文件在文件开头包含 st1
,然后是一些数据,然后是 st2
。
unsigned char *filemap; //mmap
st1 *first=(st1 *)filemap;
st2 *second=(st2 *)filemap+first->ptr_to_st2;
printf("%c",second->it);
有人告诉我此代码不正确并且违反了严格的别名规则。 编写此代码的正确方法是什么? 谢谢。
简单来说,int
有对齐要求。假设 sizeof (int)
在您的机器上是两个,我们将您的内存视为一系列块:
[a][a][b][b][c][c][d][d]...
我们可以在 [a]
块中存储一个 int
,在 [b]
块中等等...基本上每隔一个地址...但不能在它们之间。
在我们常见的家用机器上,我们可能实际上能够将它们存储在两者之间,但这是以性能成本为代价的;总线仍然对齐以检索满足对齐要求的整数,因此对于每个未对齐的整数,将通过总线进行两次检索。 这是不受欢迎的。
在不常见的家用机器上(例如旧苹果,甚至那些我们不常编程的东西,例如地球上几乎所有的路由器),这种未对齐的访问将导致类似于段错误的情况,被称为bus error。 这绝对是不受欢迎的!
如果您正确地序列化和反序列化您的信息(而不是仅仅使用类型转换来重新解释部分数组),您将不会遇到任何这些问题。也就是说,逐字节翻译你的结构,例如:
void serialise_st1(void *destination, st1 *source) {
unsigned char *d = destination;
unsigned long s = (unsigned int) source->a;
d[0] = s >> 8;
d[1] = s;
s = (unsigned int) source->b;
d[2] = s >> 8;
d[3] = s;
s = source->ptr_to_st2;
d[4] = s >> 24;
d[5] = s >> 16;
d[6] = s >> 8;
d[7] = s;
}
注意我是如何手动翻译成每个字节的吗?由于需要处理符号,反序列化过程有点困难,但本质上是相反的:我们不是单独分配给每个字节,而是单独 access 每个字节。
void deserialise_st1(st1 *destination, void *source) {
unsigned char *s = source;
*destination = (st1) { .a = (s[0] <= 127 ? s[0] : -(256 - s[0])) * 0x0100
+ s[1],
.b = (s[2] <= 127 ? s[2] : -(256 - s[2])) * 0x0100
+ s[3],
.ptr_to_st2 = (s[4] <= 127 ? s[4] : -(256 - s[4])) * 0x01000000
+ s[5] * 0x00010000
+ s[6] * 0x00000100
+ s[7] };
}
然后,根据您的示例进行调整:
unsigned char *filemap;
st1 first;
deserialise_st1(&first, filemap);
我会把它作为练习留给你写 deserialise_st2
,但如果你这样做有任何问题,请随时询问。
st2 second;
deserialise_st2(&second, filemap + st1.ptr_to_st2);
假设您的代码继续更新 first
或 second
,并且您想将这些更新推送到您的 filemap
,您需要知道它来自的偏移量... 也就是说,您需要将 filemap
关联为指向 first
(first_ptr
) 的指针,并将 filemap + st1.ptr_to_st2
关联为指向 second
的指针( second_ptr
)... 然后:
serialise_st1(first_ptr, &st1);
serialise_st2(second_ptr, &st2);