我正在尝试在固定大小的缓冲区中构建一条消息,其中我的库的用户提供了其中的一些数据。我曾经这样做过,给用户一个指向缓冲区的指针并让它们写入它,并通过引用它们写入的字节数来设置size_t
参数。我想摆脱这种方法,因为它允许用户意外破坏缓冲区,或错误地报告写入的字节数。为此,我做了以下事情:
定义了这个结构:
template <class Derived>
struct MsgBase
{
size_t size() const { return sizeof(Derived); }
const char* data() const {
const Derived* dat = static_cast<const Derived*>(this);
return reinterpret_cast<const char*>(dat);
}
};
我要求如果用户想要发送某些数据,他们会定义一个继承自此的结构,并发送要发送的数据。例如:
struct Example : MsgBase<Example>
{
int a;
double b;
char c[7];
};
我定义了这个类来帮助他们将数据传递给我的库:
class Loader
{
public:
Loader() : size(0), data(0) {}
size_t size() const { return size; }
const char* data() const { return data; }
template<class T> void loadData(const T& t) {
size = t.size();
data = t.data();
}
private:
size_t size;
const char* data;
};
所以我称之为:
{
//pos is a char* to a point in a buffer of data
Loader loader;
onLibraryCall(&loader);
memcpy(pos, loader.data(), loader.size());
}
用户正在这样做:
void onLibraryCall(Loader* loader)
{
Example e;
e.a = 3;
e.b = 2.7;
e.c[0] = //bla fill out some stuff here
loader->loadData(e);
}
这已经在我使用不同版本的gcc编译的无数二进制文件中有效,但在一个特定的二进制文件中一致地破坏了上面的消息。 gdb和valgrind根本没有帮助我,如果我尝试记录上面的调用正在发生的事情,问题就会消失。这让我觉得这里存在未定义的行为,但我不完全确定它可以在哪里或者我可以做些什么来进一步调试呢?
我检查确保任何此类已定义的结构都是POD。我也知道所有的结构是什么,目前它们都只是整体类型和固定大小的小阵列的组合。
答案 0 :(得分:2)
在Loader::loadData()
中,您通过this
存储参数MsgBase::data()
指针的副本。
在onLibraryCall()
中,您在堆栈上分配Example
实例,然后将对它的引用传递给Loader::loadData()
。 <{1}}实例在此函数结束时超出范围并被销毁。
在调用代码中,Example
返回后,onLibraryCall()
调用从memcpy()
中缓存的指针读取,但该指针现在指向的内存地址为no使用时间更长,因此您有未定义的行为。
答案 1 :(得分:0)
如果您不能保证从MsgBase
派生的所有类都是普通旧数据(POD),那么这是未定义的行为。
派生类包含指针的那一刻,你正在向网络或文件发送原始指针,当你重新加载缓冲区的内容并试图理解它时,它是无用的,可能是致命的。
任何不是POD的东西都应该被正确序列化,而不是通过流程边界发送原始数据。
小心谁也分配了什么(new / delete vs malloc vs new byte [] / delete [] ...)