2

一年多来,我一直在使用 C++ 为 Microblaze 处理器开发嵌入式软件。我的设计并没有那么复杂,所以我没有使用该语言强大的、面向对象的特性。

有一段时间,我一直在尝试增强我的设计结构。为此,我尝试广泛使用 C++ 的复杂特性,例如继承、多态等。作为新手,我相信单独使用继承不会影响代码大小。只有多态性有一些副作用,例如添加虚拟表指针、运行时类型信息等。我的问题始于向基类添加纯虚拟成员函数。

为了提供一个可运行的示例,我将尝试模仿我所面临的情况。

下面的代码编译并生成13292 字节的代码。这段代码不可能有这么多的指令。但是,我相信生成的 BSP 中的某些部分是在生成 elf 文件时必须包含的。

class Base{
public:
    Base() = default;
    ~Base() = default;
  
    virtual void func() {}
  
    int m_int;
};

class Derived : public Base{
public:
    Derived() = default;
    ~Derived() = default;
    
    void func() final {}
  
    int m_int2;
};

int main()
{
    Derived d;
  
    while(1);    
}
 

当您认为您有近 128KB 的可用 RAM 时,13KB并不算多。实际上,直到出现纯虚函数的问题时,我才注意到生成的代码的大小。下面的第二个代码具有相同的结构,只是func()现在是一个纯虚函数。构建此代码为我们提供了一个大于可用*(128KB)* RAM 大小的代码大小。因此,我修改了链接器文件以添加一些假 RAM,以便能够编译代码。编译成功后,生成的代码大小接近157KB!

class Base{
public:
    Base() = default;
    ~Base() = default;
  
    virtual void func() = 0;
  
    int m_int;
};

class Derived : public Base{
public:
    Derived() = default;
    ~Derived() = default;
    
    void func() final {}
  
    int m_int2;
};

int main()
{
    Derived d;
  
    while(1);    
}

我没有更改编译器的任何首选项,所有参数都处于默认状态。除了自动生成的库之外,没有其他库。你认为问题可能是什么?

一些附加说明

  • 我在两个不同的 IDE 上尝试了代码。Vivado SDK 2017.2 和 Vitis 2019.2
  • 同样的问题也适用于动态分配调用(操作员 new 和 delete)。用 C 风格的 malloc 和 free 替换它们可以解决问题。
  • 以发布模式构建代码也解决了这个问题。在发布模式下,无论我是否使用纯虚函数,生成的代码都是 1900 字节。

如果需要,我可以提供更多信息,谢谢

我在赛灵思论坛上问过同样的问题,你可以在 这里找到

4

1 回答 1

1

解决方案有点令人毛骨悚然:) 在开始之前,特别感谢所有提供帮助的人。

简短的回答

只需将以下代码段添加到您的主文件中:

extern "C" void __cxa_pure_virtual() { while(1); }

如果您还想解决与operator new和相关的问题operator delete,还请添加以下代码:

void* operator new(const std::size_t size) noexcept
{
    void* p = std::malloc(size);
    return p;
}

void operator delete(void* p) noexcept
{
    std::free(p);
}

细节

原来的解决方案在这里。问题始于将libstdc++完全排除在外。这样我们就放弃了使用标准库函数的权利,所以我们应该提供我们自己的标准调用的实现,例如malloc, new,free等。即使你重新实现所有必需的调用,编译器也会抱怨缺少一个名为 的函数__cxa_pure_virtual()。这是最终解决方案的线索。

__cxa_pure_virtual函数是在调用纯虚函数时调用的错误处理程序。我们可以很容易地说,我们从来没有做过这种愚蠢的尝试。但是,编译器从不信任任何软件开发人员:) 因此,当您编写包含纯虚函数的 C++ 代码时,编译器会隐式添加错误处理程序来处理潜在的运行时错误。正如您可以猜到的那样,对于资源有限的系统(例如我们的 Microblaze)来说,这些都是昂贵的调用。

因此,如果我们正在编写一个具有纯虚函数的 C++ 应用程序,我们将提供我们自己的__cxa_pure_virtual错误处理函数。如果你不是一个有竞争力的嵌入式软件开发者,你应该在你的自定义处理函数中添加一个无穷无尽的东西。不用担心,只要您遵循语言的最佳实践,您将永远没有机会调用调用错误处理程序的纯虚函数。

operator new和的问题operator delete也与底层异常机制有关。为了避免昂贵的异常处理机制,您可以以不引发任何异常的方式重新实现它们。您应该考虑的唯一一件事是在调用之后检查分配是否成功,operator new因为它不会再产生异常。我相信operator delete只要你在一个无操作系统的应用程序项目上工作,你就永远不需要调用。

在您自己的代码上应用这个神圣的秘诀后,您会看到可执行文件的大小将回落到其原始状态。

答案对贡献和建议开放。如果你能做到,我将不胜感激

于 2021-04-16T12:08:04.140 回答