如何避免模板实例化和符号表发出的C ++代码膨胀?

时间:2017-12-19 19:04:08

标签: c++11 templates gcc bare-metal

几年前,我开始了一个裸机(Cortex-M)项目。在项目设置中,我们决定使用gcc工具链和C ++ 11 / C ++ 14等启用,甚至使用C ++异常和rtti。

我们目前正在使用gcc 4.9 from launchpad.net/gcc-arm-embedded(有一些问题阻止我们目前更新到更新的gcc版本)。

例如,我写了一个基类和这样的派生类(另请参见运行示例here):

12

因此,我班级的客户现在可以使用例如:

class OutStream {
public:
    explicit OutStream() {}
    virtual ~OutStream() {}
    OutStream& operator << (const char* s) {
        write(s, strlen(s));
        return *this;
    }
    virtual void write(const void* buffer, size_t size) = 0;    
};

class FixedMemoryStream: public OutStream {
public:
    explicit FixedMemoryStream(void* memBuffer, size_t memBufferSize): memBuffer(memBuffer), memBufferSize(memBufferSize) {}
    virtual ~FixedMemoryStream()       {}
    const void*  getBuffer() const     { return memBuffer; }
    size_t       getBufferSize() const { return memBufferSize; }
    const char*  getText() const       { return reinterpret_cast<const char*>(memBuffer); }  ///< returns content as zero terminated C-string    
    size_t       getSize() const       { return index; }                                     ///< number of bytes really written to the buffer (max = buffersize-1)
    bool         isOverflow() const    { return overflow; }
    virtual void write(const void* buffer, size_t size) override { /* ... */ }
private:
    void*  memBuffer = nullptr;   ///< buffer
    size_t memBufferSize = 0;     ///< buffer size
    size_t index = 0;             ///< current write index
    bool   overflow = false;      ///< flag if we are overflown
};

现在我想让这个类的使用更加舒适,并介绍了以下模板:

char buffer[10];
FixedMemoryStream ms1(buffer, sizeof(buffer));
ms1 << "Hello World";

从现在开始,我的客户可以写下:

template<size_t bufferSize> class FixedMemoryStreamWithBuffer: public FixedMemoryStream {
public:
    explicit FixedMemoryStreamWithBuffer(): FixedMemoryStream(buffer, bufferSize) {}
private:
    uint8_t buffer[bufferSize];
};

但从现在开始,我发现可执行二进制文件的大小越来越大。似乎gcc为FixedMemoryStreamWithBuffer<10> ms2; ms2 << "Hello World"; 的每个不同模板实例化添加了符号信息(因为我们出于某种原因使用了rtti)。

可能有办法仅针对某些特定的类/模板/模板实例来删除符号信息吗?

可以为此获得非便携式gcc解决方案。

出于某种原因,我们决定更喜欢模板而不是预处理器宏,我想避免使用预处理器解决方案。

2 个答案:

答案 0 :(得分:2)

是的,有一种方法可以将必要的符号几乎降到0:使用标准库。您的OutStream课程是std::basic_ostream的简化版。您的OutStream::write实际上只是std::basic_ostream::write,依此类推。看看它here。然而,溢出处理非常紧密,为了完整起见,它还处理underflow,即需要数据检索;您可以将其保留为未定义(它也是virtual)。

同样,您的FixedMemoryStreamstd::basic_streambuf<T>,其中包含固定大小(std::array<T>)的获取/投放区域。

所以,只要让你的类继承自标准的类,你就会切断二进制大小,因为你正在重用已经声明的符号。

现在,关于template<size_t bufferSize> class FixedMemoryStreamWithBuffer。此类与std::array<std::uint8_t, bufferSize>非常类似于指定和获取内存的方式。你无法对此进行优化:每个实例都是不同的类型,包含所有含义。编译器不能“合并”或对它们做任何魔术:每个实例化必须有自己的类型。 因此,要么退回到std::vector,要么有一些固定大小的专用块,如32,128等,并且对于中间的任何值都会选择正确的块;这可以完全在编译时实现,因此没有运行时成本。

答案 1 :(得分:2)

首先,请记住,编译器还为每个FixedMemoryStreamWithBuffer&lt;&gt;生成单独的v表(以及RTTI信息)。类型实例,以及继承链中的每个类。

为了解决这个问题,我建议在内部使用一些转换函数和/或运算符来使用包含而不是继承:

    template<size_t bufferSize> 
    class FixedMemoryStreamWithBuffer
    {
         uint8_t buffer[bufferSize];
         FixedMemoryStream m_stream;
    public:
        explicit FixedMemoryStreamWithBuffer() : m_stream(m_buffer, bufferSize) {}
        operator FixedMemoryStream&() { return m_stream; }
        FixedMemoryStream& toStream() { return m_stream; }
   };