CRTP的这个用例是否被认为是未定义的行为?

时间:2014-09-18 15:46:23

标签: c++ memory undefined-behavior crtp

我正在尝试在固定大小的缓冲区中构建一条消息,其中我的库的用户提供了其中的一些数据。我曾经这样做过,给用户一个指向缓冲区的指针并让它们写入它,并通过引用它们写入的字节数来设置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。我也知道所有的结构是什么,目前它们都只是整体类型和固定大小的小阵列的组合。

2 个答案:

答案 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 [] ...)