构建一个具有可变数据量的对象

时间:2010-12-24 04:54:44

标签: c++ memory-management

我正在开发一个消息传递运行时系统,当消息包含可变长度数据时,该系统的现有消息分配代码如下所示:

struct MsgBase {
  void* operator new(size_t obj_size, int arr1_size, int arr2_size);
};
struct Msg : MsgBase {
  double *arr1;
  double *arr2;
};
struct MsgBase {
  void* operator new(size_t obj_size, int arr1_size, int arr2_size) {
    size_t offsets[2];
    offsets[0] = obj_size;
    offsets[1] = obj_size + sizeof(double)*arr1_size;
    Msg *m = (Msg *)malloc(offsets[1] + sizeof(double)*arr2_size);
    m->arr1 = (char*)m + sizeof(double)*arr1_size;
    m->arr1 = (char*)m->arr1 + sizeof(double)*arr2_size;
    return m;
  }
};

出于硬件接口的原因,消息必须被分配为一个大缓冲区,并且在事实之后复制到这样的缓冲区会导致性能 1

和(很多)客户端代码

Msg *msg = new (12, 17) Msg;
msg->arr1[6] = 543.43;

我们刚刚遇到的问题是g ++ 4.4(与早期版本不同)在指针返回给我们之前从sizeof(Msg)开始将msg字节清零,因此那些偏移指针进入缓冲区不保留。因此,第二行代码会导致段错误。

如果我们声明构造函数Msg::Msg(),则不会发生归零,但我的直觉是编译器在调用构造函数之前将其权限归零。

  • 那么,我的直觉是否正确,即使我们明确声明了构造函数,这样的代码也没有真正的工作保证?

  • 假设上面的代码不能继续工作,我是否有希望保留客户端界面?如何替换这种外观?

<小时/> 注意:MsgBase类和助理operator new()是从客户端提供的接口定义生成的,该定义类似于

message Msg {
  double arr1[];
  double arr2[];
};

虽然客户端代码负责定义Msg本身并确保它继承自MsgBase。因此,我们可以更改MsgBase的任何内容,但对Msg几乎没有任何改变,而不会在现有应用程序代码上强制执行此操作。

  1. 别告诉我去个人资料。我们对此进行了大量测试(我们在top500的前10名中运行),并且尽可能地尝试零拷贝。我们目前没有在这里制作副本,回归也很糟糕。

1 个答案:

答案 0 :(得分:1)

你是对的,operator new写入内存的任何内容都不能保证保留。所以不,你不能保留当前的破坏调用语法。

使用工厂功能和库提供的新位置。像这样:

struct Msg
{
  double* const arr1;
  double* const arr2;
private:
  Msg(double* p1, double* p2) : arr1(p1), arr2(p2) {}
  Msg(const Msg&); // deleted copy-constructor
  // having const members prevents assignment operator from being implicitly generated
public:
  static Msg* Create( size_t arr1_len, size_t arr2_len )
  {
      void* raw = ::operator new(sizeof (Msg) + (1 + arr1_len + arr2_len) * sizeof (double));
      // note ugly math to properly align double, assumes sizeof (double) is a power of 2
      // consider using alignof (double) instead of sizeof (double) if your compiler supports it
      double* p = reinterpret_cast<double*>((reinterpret_cast<intptr_t>(raw) + sizeof (Msg) + sizeof (double)) & ~(sizeof (double) - 1));
      return new (raw) Msg(p, p + arr1_len);
  }
};

注意:您可以通过使用线程局部变量和构造函数来保留当前语法...基本上您的自定义operator new会将指针或大小放入TLS中,然后构造函数会读取TLS并适当设置指针。我想我会传递大小,因为编译器可能会要求operator new一些额外的内存并在实际对象前填充调试信息。