首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >静态分配和放置新的结果为空指针取消引用

静态分配和放置新的结果为空指针取消引用
EN

Stack Overflow用户
提问于 2018-12-14 17:12:36
回答 1查看 358关注 0票数 0

我正在开发一个不鼓励堆分配的嵌入式平台。在构造过程中,我也有循环依赖关系。考虑到这些约束,我的团队设计了一个静态分配器类,用于在.bss部分分配内存,然后以延迟的方式构造对象。

我们面临的问题是,在延迟构造过程中,编译器生成的代码试图引用尚未构建的静态分配内存中的数据--未构造时我们平台上的数据为零--这将导致系统出现空指针取消引用。

崩溃可以通过重新排序类的构造顺序来解决。不幸的是,我没有能够创造一个最低限度的复制问题。此外,当涉及虚拟继承时,问题变得越来越严重,难以管理。

我们已经经历了针对armclang和visual studio编译器的问题,因此我们似乎正在做一些超出C++规范的事情。

静态分配器代码:

代码语言:javascript
复制
template <class UnderlyingType, typename... Args>
class StaticAllocator
{
private:
    typedef std::uint64_t BaseDataType;

    // Define a tuple of the variadic template parameters with the references removed
    using TupleWithRefsRemoved = std::tuple<typename std::remove_reference<Args>::type...>;

    // A function that strips return the ref-less template arguments
    template <typename... T>
    TupleWithRefsRemoved removeRefsFromTupleMembers(std::tuple<T...> const& t)
    {
        return TupleWithRefsRemoved{ t };
    }

public:
    StaticAllocator()
    {
        const auto ptr = reinterpret_cast<UnderlyingType *>(&m_underlyingData);
        assert(ptr != nullptr);
    }

    virtual StaticAllocator* clone() const
    {
        return new StaticAllocator<UnderlyingType, Args...>(*this);
    }

    UnderlyingType *getPtr()
    {
        return reinterpret_cast<UnderlyingType *>(&m_underlyingData);
    }

    const UnderlyingType *getPtr() const
    {
        return reinterpret_cast<const UnderlyingType *>(&m_underlyingData);
    }

    UnderlyingType *operator->()
    {
        return getPtr();
    }

    const UnderlyingType *operator->() const
    {
        return getPtr();
    }

    UnderlyingType &operator*()
    {
        return *getPtr();
    }

    const UnderlyingType &operator*() const
    {
        return *getPtr();
    }

    operator UnderlyingType *()
    {
        return getPtr();
    }

    operator const UnderlyingType *() const
    {
        return getPtr();
    }

    void construct(Args... args)
    {
        _construct(TupleWithRefsRemoved(args...), std::index_sequence_for<Args...>());
    }

    void destroy()
    {
        const auto ptr = getPtr();
        if (ptr != nullptr)
        {
            ptr->~T();
        }
    }

private:
    BaseDataType m_underlyingData[(sizeof(UnderlyingType) + sizeof(BaseDataType) - 1) / sizeof(BaseDataType)];

    // A function that unpacks the tuple of arguments, and constructs them
    template <std::size_t... T>
    void _construct(const std::tuple<Args...>& args, std::index_sequence<T...>)
    {
        new (m_underlyingData) UnderlyingType(std::get<T>(args)...);
    }
};

简单用法示例:

代码语言:javascript
复制
class InterfaceA
{
    // Interface functions here
}

class InterfaceB
{
    // Interface functions here
}


class ObjectA : public virtual InterfaceA
{
public:
    ObjectA(InterfaceB* intrB) : m_intrB(intrB) {}

private:
    InterfaceB* m_intrB;
};

class ObjectB : public virtual InterfaceB
{
public:
    ObjectB(InterfaceA* intrA) : m_intrA(intrA) {}

private:
    InterfaceA* m_intrA;
}

StaticAllocator<ObjectA, InterfaceB*> objectAStorage;
StaticAllocator<ObjectB, InterfaceA*> objectBStorage;

// Crashes happen in this function, there are many more objects in our real
// system and the order of the objects effects if the crash occurs.
void initialize_objects()
{
    auto objA = objectAStorage.getPtr();
    auto objB = objectBStorage.getPtr();

    objectAStorage.construct(objB);
    objectBStorage.construct(objA);
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-12-19 03:58:38

这个答案描述了运行时发生的问题,以GCC为例。其他编译器将生成具有类似问题的不同代码,因为您的代码具有缺乏初始化的固有问题。

如果不为了提高效率而避免动态内存分配,没有通用方法,没有模板,每一步都被分解,那么您的代码实际上可以归结为:

代码语言:javascript
复制
class InterfaceA {};

class InterfaceB {};

class ObjectA : public virtual InterfaceA {
 public:
  ObjectA(InterfaceB *intrB) : m_intrB(intrB) {}

 private:
  InterfaceB *m_intrB;
};

class ObjectB : public virtual InterfaceB {
 public:
  ObjectB(InterfaceA *intrA) : m_intrA(intrA) {}

 private:
  InterfaceA *m_intrA;
};

#include <new>

void simple_init() {
  void *ObjectA_mem = operator new(sizeof(ObjectA));
  void *ObjectB_mem = operator new(sizeof(ObjectB));
  ObjectA *premature_ObjectA = static_cast<ObjectA *>(ObjectA_mem);  // still not constructed
  ObjectB *premature_ObjectB = static_cast<ObjectB *>(ObjectB_mem);
  InterfaceA *ia = premature_ObjectA;  // derived-to-base conversion
  InterfaceB *ib = premature_ObjectB;
  new (ObjectA_mem) ObjectA(ib);
  new (ObjectB_mem) ObjectB(ia);
}

为了最大限度地提高编译代码的可读性,让我们用全局变量编写它:

代码语言:javascript
复制
void *ObjectA_mem;
void *ObjectB_mem;
ObjectA *premature_ObjectA;
ObjectB *premature_ObjectB;
InterfaceA *ia;
InterfaceB *ib;

void simple_init() {
  ObjectA_mem = operator new(sizeof(ObjectA));
  ObjectB_mem = operator new(sizeof(ObjectB));
  premature_ObjectA = static_cast<ObjectA *>(ObjectA_mem);  // still not constructed
  premature_ObjectB = static_cast<ObjectB *>(ObjectB_mem);
  ia = premature_ObjectA;  // derived-to-base conversion
  ib = premature_ObjectB;
  new (ObjectA_mem) ObjectA(ib);
  new (ObjectB_mem) ObjectB(ia);
}

那个给我们一个非常好的汇编代码。我们可以看到,这一声明:

代码语言:javascript
复制
  ia = premature_ObjectA;  // derived-to-base conversion

汇编成:

代码语言:javascript
复制
        movq    premature_ObjectA(%rip), %rax
        testq   %rax, %rax
        je      .L6
        movq    premature_ObjectA(%rip), %rdx
        movq    premature_ObjectA(%rip), %rax
        movq    (%rax), %rax
        subq    $24, %rax
        movq    (%rax), %rax
        addq    %rdx, %rax
        jmp     .L7
.L6:
        movl    $0, %eax
.L7:
        movq    %rax, ia(%rip)

首先,我们看到(未优化的)代码测试一个空指针,相当于

代码语言:javascript
复制
if (premature_ObjectA == 0) 
  ia = 0;
else
  // real stuff

真正的东西是:

代码语言:javascript
复制
    movq    premature_ObjectA(%rip), %rdx
    movq    premature_ObjectA(%rip), %rax
    movq    (%rax), %rax
    subq    $24, %rax
    movq    (%rax), %rax
    addq    %rdx, %rax
    movq    %rax, ia(%rip)

因此,premature_ObjectA指向的值被取消引用,解释为指针,减少24,结果指针用于读取值,该值被添加到原始指针premature_ObjectA。由于premature_ObjectA的内容未初始化,因此显然无法工作。

正在发生的情况是,编译器将vptr (vtable指针)从0级读取-3 "quad“(3*8 = 24)项(像建筑物一样的vtable可以有负层,这只是意味着第0层不是最低层):

代码语言:javascript
复制
vtable for ObjectA:
        .quad   0
        .quad   0
        .quad   typeinfo for ObjectA
vtable for ObjectB:
        .quad   0
        .quad   0
        .quad   typeinfo for ObjectB

vtable (每个对象的vtable)开始于其末尾,在"typeinfo for ObjectA“之后,正如我们在ObjectA::ObjectA(InterfaceB*)编译代码中看到的那样。

代码语言:javascript
复制
        movl    $vtable for ObjectA+24, %edx
...
        movq    %rdx, (%rax)

因此,在构造过程中,vptr设置为vtable的“零层”,它位于第一个虚拟函数之前,如果没有虚拟函数,则在最后。

在第三层,有vtable的开头:

代码语言:javascript
复制
vtable for ObjectA:
        .quad   0

值0表示"InterfaceA在一个完整的ObjectA对象中的偏移量为0“。

vtable布局的细节将取决于编译器,原则如下:

  • 构造函数中的vptr隐藏数据成员(可能还有多个其他隐藏成员)的初始化
  • 在转换为InterfaceA基类时使用这些隐藏成员

保持原样。

我的解释没有提供修复:我们甚至不知道您有什么样的高级别问题,以及为什么要使用这些构造函数参数和相互依赖的类。

知道这些类代表了什么,我们也许能够提供更多帮助。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/53784159

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档