数据对齐(序列化)的便携式解决方案

Portable solution to data alignment (Serialization)

本文关键字:便携式 解决方案 序列化 对齐 数据      更新时间:2023-10-16

假设我正在尝试从文件中读取数据。数据存储为二进制数据,可以使用编译器打包扩展轻松读取(为清楚起见,使用 C99 表示法(:

#pragma pack(push, 1) /* visual studio */
struct S
{
  int16_t v1;
  uint32_t v2; /* gcc would use: __attribute__((packed)) */
  int16_t v3;
};
#pragma pack(pop)
void read( std::istream & is )
{
  S s;
  assert( sizeof(s) == 8 ); // packed !
  is.read( (char*)&s, sizeof(s) );
  std::cout << s.v1 << " " << s.v2 << " " << s.v3 << std::endl;
}

编写相同的代码会变得更加混乱,但要考虑到可移植性:

struct S2
{
  unsigned char buf1[2];
  unsigned char buf2[4];
  unsigned char buf3[2];
};
static inline uint16_t makenum(const unsigned char (&x)[2])
{
  return x[0] | (x[1] << 8);
}
static inline uint32_t makenum(const unsigned char (&x)[4])
{
  return
      ((uint32_t)x[0] <<  0)
    | ((uint32_t)x[1] <<  8)
    | ((uint32_t)x[2] << 16)
    | ((uint32_t)x[3] << 24);
}
void read( std::istream & is )
{
  S2 s2;
  assert( sizeof(s2) == 8 ); // garanteed !
  is.read( (char*)&s2, sizeof(s2) );
  std::cout << makenum(s2.buf1) << " " << makenum(s2.buf2) << " " << makenum(s.buf3) << std::endl;
}

还有什么(更聪明的(要做吗?我想位移和按位包含 OR 应该不会对执行产生太大影响,但我找不到具有union来避免计算的通用解决方案。例如:伪解(非工作(:

struct S3
{
  union { char buf1[2]; int16_t v1; } uv1;
  union { char buf2[4]; uint32_t v2; } uv2;
  union { char buf3[2]; int16_t v3; } uv3;
};

我强烈建议不要使用struct将字段 1:1 与协议映射

原因之一是允许编译器在字段之间添加填充。

另一个是您希望该结构对处理器有效。 希望您能更频繁地操作结构中的数据,以便使用它执行 I/O。

例如,给定一个 32 位

处理器对 32 位数字非常有效,但对于 16 位则不那么高效。 该协议需要 16 位整数。 那么,您将结构中的字段映射为 16 位还是 32 位?

答:在结构中使用 32 位字段和 write 方法与协议进行转换。例如,要从内存加载 16 位变量,32 位处理器可能必须执行读取和移位,具体取决于 16 位在 32 位寄存器中的位置。 如果结构字段为 32 位,则不需要移位;从而更高效。

此外,编写协议转换函数允许您在不更改结构的情况下处理大端序与小端序问题。

这是我提出的解决方案:

#include <cstring>
#include <stdint.h>
template <typename T>
struct Fast;
template <>
struct Fast<uint16_t> {
  typedef uint_fast16_t Type;
};
template <>
struct Fast<int16_t> {
  typedef int_fast16_t Type;
};
template <>
struct Fast<uint32_t> {
  typedef uint_fast32_t Type;
};
template <>
struct Fast<int32_t> {
  typedef int_fast32_t Type;
};
template <>
struct Fast<uint64_t> {
  typedef uint_fast64_t Type;
};
template <>
struct Fast<int64_t> {
  typedef int_fast64_t Type;
};
template <typename T>
struct Helper {
  typedef typename Fast<T>::Type RetType;
  typedef char (VecType)[sizeof(T)];
  typedef union { VecType vec; T val; } UType;
};
template <typename T>
struct MakeNum {
  typedef typename Helper<T>::RetType RetType;
  typedef typename Helper<T>::UType UType;
  static RetType Get(const char (&x)[sizeof(T)]) {
    UType u;
    memcpy( u.vec, x, sizeof(T) );
    return u.val;
  }
};

#define AddField( type, name ) 
  char name ## _[sizeof(type)]; 
  typename MakeNum<type>::RetType 
    name () const { return MakeNum<type>::Get(name ## _); }
struct S
{
  AddField( uint16_t, name1 );
  AddField(  int32_t, name2 );
  AddField( uint16_t, name3 );
};
int main()
{
  S s = { 0, 1, 0, 1, 0, 0, 0, 1 };
  return s.name1() + s.name2() + s.name3();
}

它确实使用 gcc 4.9.1 生成与此(不可移植(完全相同的代码:

#include <stdint.h>
struct S2
{
  uint16_t v1;
   int32_t v2 __attribute__((packed));
  uint16_t v3;
};
int main()
{
  S2 u = { 256, 256, 256 };
  return u.v1 + u.v2 + u.v3;
}