使用STL会显着增加占地面积吗?

时间:2008-12-15 00:42:13

标签: c++ stl

使用STL会显着增加占地面积吗?你们可以分享一下你对此事的经验吗?构建小型库的最佳实践是什么?

8 个答案:

答案 0 :(得分:20)

由于STL是一组模板,所以没有一个答案。模板本质上只在使用时编译。因此,您可以包含所有STL,如果没有实际使用,则STL添加的占用空间将为零。如果你有一个非常小的应用程序,它设法使用许多具有不同专业的不同模板,那么占用空间就会很大。

答案 1 :(得分:10)

关于STL的事情因为它是所有模板,它只会在实际使用时增加大小。如果您不使用方法,那么该方法不会被实例化。

但是你使用的东西总会有成本。

但是你要问的真正的问题。尺寸是否比你自己的实施更大或更小?如果您不使用STL,您使用什么?你可以自己动手写,但这有自己的成本。它不是零空间,它不会经过良好测试,您将不会遵循既定的最佳实践。

所以实际上没有它不会破坏你的代码 因为通过其他方法添加等效功能将添加尽可能多的代码,它不会经过充分测试。

其他帖子指出模板代码必须是内联的或具有多个定义 这绝对是错误

方法标记为内嵌。但是,如果该方法实际上是内联的,则由编译器决定。编译器内联非常复杂,只有在内联才能实现优化策略。

如果没有内联,那么将在使用该方法的每个编译单元中生成该方法的副本。但是,对于C ++链接器的要求是,当应用程序链接到可执行文件时,它必须删除这些方法的所有副本。如果编译器没有删除额外的副本,则必须生成多定义链接器错误。

这很容易显示:
以下说明:

  • 方法_M_insert_aux()不是内联的。
  • 它放在两个编译单元中。
  • 该方法的副本只在最终的可执行文件中。

a.cpp

#include <vector>

void a(std::vector<int>& l)
{
    l.push_back(1);
    l.at(0) = 2;
}

b.cpp

#include <vector>

void b(std::vector<int>& l)
{
    l.push_back(1);
    l.at(0) = 2;
}

的main.cpp

#include <vector>

void a(std::vector<int>&);
void b(std::vector<int>&);

int main()
{
    std::vector<int>    x;
    a(x);
    b(x);
}

检查

>g++ -c a.cpp
>g++ -c b.cpp

>nm a.o
<removed other stuff>
000000a0 S __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi
<removed other stuff>

>nm b.o
<removed other stuff>
000000a0 S __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi
<removed other stuff>

>c++filt __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi
std::vector<int, std::allocator<int> >::_M_insert_aux(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int const&)

>g++ a.o b.o main.cpp
nm a.out | grep __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi
00001700 T __ZNSt6vectorIiSaIiEE13_M_insert_auxEN9__gnu_cxx17__normal_iteratorIPiS1_EERKi

答案 2 :(得分:4)

使用STL会增加二进制大小,原因有两个:

内联

默认情况下,编译器将模板代码视为内联。因此,如果在几个不同的编译单元中使用std::list<int>和编译器内联代码,它们每个都有自己的std::list<int>函数的本地内联定义(可能这不是什么大问题,因为编译器默认只会内联非常小的定义。)

请注意(正如Martin在其他地方指出的那样)多重定义的符号被大平台上的所有现代C ++链接器剥离,如GCC documentation中所述。因此,如果编译器将模板代码保留为脱节,则链接器将删除重复项。

专业化

由于C ++模板的本质,std::list<int>可能与std::list<double>任意不同。实际上,标准要求std::vector<bool>被定义为位向量,因此大多数操作完全与默认std::vector<T>不同。

只有图书馆维护者可以处理这个问题。一种解决方案是采用核心功能并对其进行“非模板化”。将其转换为C样式的数据结构,其中void*无处不在。然后,下游开发人员看到的模板接口是一个瘦包装器。减少了重复代码的数量,因为模板专业化都有共同的基础。

答案 3 :(得分:2)

我假设你的意思是运行时内存足迹,因此STL 容器

STL容器对于它们来说是有效的......通用容器。如果您正在决定编写自己的双向链表或使用std :: list,请...使用STL。如果您正在考虑针对您的每个特定需求编写非常特定于域的容器,请先使用STL,然后在所有代码正常工作后选择您的战斗。

一些好的做法:

  • 如果您的库要通过API公开这些容器,您可能需要选择将STL代码放在库标题中还是不使用STL。问题是我的编译器不必像你那样实现STL。
  • 了解STL的方式和时间 容器分配内存。什么时候 你可以想象一下deque如何成长 与矢量相比缩小 你会更好地决定使用哪个。
  • 如果您需要对每个字节进行微管理,请考虑编写自定义分配器。这在嵌入式系统之外很少是必要的。

答案 4 :(得分:2)

虽然它没有解决STL模板,但GCC的文档中有关于在使用模板模板时最大限度地减少代码膨胀的简短部分。

链接是 http://gcc.gnu.org/onlinedocs/gcc-4.3.2/gcc/Template-Instantiation.html#Template-Instantiation

答案 5 :(得分:1)

基于指针的STL特化可以共享相同的实现。这是因为(void *)与(int *)或(foo *)具有相同的大小。所以,同时:

vector⟨int⟩和vector⟨foo⟩是不同的实现。

vector⟨int*⟩和vector⟨foo*⟩可以共享大部分相同的实现。许多STL实现都是为了节省内存占用。

如果整个模板在头文件中定义以便完全定义,那么像g ++这样的编译器将使用该类在每个编译单元中自动创建一个副本。正如其他人所说,链接器将删除多个定义。

对于更高的优化级别,它们还将自动内联类定义中的方法。但这可以通过编译器选项来控制。

答案 6 :(得分:1)

暂时忽略STL讨论,构建低占用空间的静态库有一个非显而易见的重要实践。将库分区为尽可能多的不同编译单元。例如,如果查看libpthread.a,您将看到每个函数都有自己的编译单元。很多链接器会根据整个编译单元丢弃死代码,但不会再那么细粒度了。如果我只使用pthreads库中的一些函数,我的链接器将只引入那些定义,而不是其他任何内容。另一方面,如果将整个库编译成单个目标文件,我的链接器必须将整个库作为单个“单元”引入。

这取决于您正在使用的工具链,它仅适用于构建静态库的情况。但我已经看到它对于次优构建的大型库产生了非常可测量的差异。

答案 7 :(得分:0)

在我曾经做过64kb限制的嵌入式项目中,我甚至无法链接标准C库。所以这取决于你需要做什么