【问题标题】:Reading struct from mmap从 mmap 读取结构
【发布时间】:2015-07-24 02:52:39
【问题描述】:
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);

我被告知此代码不正确并且违反了严格的别名规则。 编写此代码的正确方法是什么? 谢谢。

【问题讨论】:

标签: c mmap


【解决方案1】:

简单地说,int 有对齐要求。假设sizeof (int) 在您的机器上是两个,我们将您的内存视为一系列块:

[a][a][b][b][c][c][d][d]...

我们可以在[a] 块、[b] 块等中存储一个int...基本上在每个第二个地址...但不是在它们之间。

在我们常见的家用机器上,我们也许实际上能够将它们存储在它们之间,但这是以性能为代价的;总线仍然对齐以检索满足对齐要求的整数,因此对于每一个未对齐的整数,都会通过总线进行两次检索。 这是不受欢迎的。

在不常见的家用机器上(例如旧苹果,甚至是我们不常用编程的东西,例如地球上几乎所有路由器),这种未对齐的访问将导致类似于段错误的情况,称为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;
}

注意我是如何手动翻译成每个字节的?由于需要处理符号,反序列化过程有点困难,但本质上是相反的:我们不是单独分配给每个字节,而是单独访问每个字节。

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);

假设您的代码继续更新 firstsecond,并且您想将这些更新推送到您的 filemap,您需要知道它来自的偏移量...也就是说,您我想将filemap 关联为指向first (first_ptr) 的指针,并将filemap + st1.ptr_to_st2 作为指向second (second_ptr) 的指针...然后:

serialise_st1(first_ptr, &st1);
serialise_st2(second_ptr, &st2);

【讨论】:

  • 感谢您的热心回复,我有几个问题,对于每个CPU,我需要根据CPU类型的大小编写不同的序列化函数,对吗?您能解释一下为什么将source-&gt;a 转换为unsigned int 并右移一个字节吗? unsigned long s = (unsigned int) source-&gt;a; d[0] = s &gt;&gt; 8;
  • @Neet33 不。您应该根据标准 C 中所需的范围选择整数,并为此进行序列化。也就是说,如果您要使用 -32767 到 32767 范围内的值,则使用 int... 否则,如果您打算使用 -2147483647 到 2147483647 范围内的值,请使用 long。否则,请考虑使用long long
  • @Neet33 我将有符号值转换为无符号值,以将符号位编码为负值;这是我能想到的最干净的方式。左移提取 16 位值的高字节。
  • 我还有一个问题希望你能帮助我,如果我知道哪个系统将运行该程序,那还有另一种更好的方法可以写这个吗?也许直接从记忆中阅读? (就像我在问题中的例子一样,只是没有违反严格的别名规则。)
  • 你好,我需要帮助来处理这个typedef struct { union { char a[8]; struct { unsigned long z; unsigned long o; } ss; } ss; } st3;你能帮我吗?谢谢。
猜你喜欢
  • 1970-01-01
  • 2016-09-11
  • 1970-01-01
  • 2010-09-07
  • 1970-01-01
  • 2022-12-09
  • 1970-01-01
  • 2015-04-06
  • 1970-01-01
相关资源
最近更新 更多