我正在开发一个消息传递运行时系统,当消息包含可变长度数据时,该系统的现有消息分配代码如下所示:
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
几乎没有任何改变,而不会在现有应用程序代码上强制执行此操作。
答案 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
一些额外的内存并在实际对象前填充调试信息。