如果我想知道用标准 C++ 库编写的函数是如何工作的(不仅仅是 MSDN 描述)。我的意思是它如何分配、管理、释放内存并返回结果。您需要在哪里或需要知道什么才能理解这一点?
5 回答
您可以查看库标题。那里实际上实现了很多功能,因为该库是高度模板化的(并且模板通常需要在标头中实现)。头文件的位置取决于编译器,但您应该能够很容易地找到它们(例如搜索名为 algorithm 的文件)。
您还可以要求编译器预处理您的代码以查看所有相关的标头(这将产生极长的输出)。使用 GCC,您可以通过g++ -E yoursource.cc
.
如果您要查找的内容未在标头中实现,则需要库源,默认情况下通常不安装,甚至对于 MSVC 等商业编译器也不可用。查找 glibc(C 库)和 libstdc++(C++ 库),它们是 GCC 和其他一些编译器使用的。
在任何情况下,请注意标准库的实现往往是相当神秘的,因为在变量名称等中使用了很多下划线(以避免与用户的宏发生名称冲突),而且它们通常也被 #ifdefs 和其他预处理器所感染粗鲁。
您需要了解用于编写 C++ 库的技术。获得Bjarne Stroustrup 的书是一个好的开始。此外,SGI有非常详细的关于 STL 的适当高抽象级别的文档。
如果您要研究基于 Windows 的东西,您可能需要研究Windows 库的系统部分。
作为 windows 的补充:理解Posix规范也很重要。
分配/释放内存的操作系统函数与 C++ 标准库并不真正相关。
标准库容器(默认情况下)将使用 new 和 delete 作为内存,并且使用特定于编译器的运行时,它几乎可以肯定地管理自己的堆数据结构。这种方法通常更适合典型应用程序使用,其中特定于平台的操作系统堆通常更适合分配大块。
应用程序堆将从操作系统堆中分配/释放内存,但是“如何?” 什么时候?” 是特定于平台和特定于编译器的详细信息。
对于 Win32 内存管理 API,请看这里...
http://msdn.microsoft.com/en-us/library/ms810603.aspx
我敢肯定,如果需要,您可以找到 win64 等价物。
首先是一些基本的数据结构原则,然后是关于分配器的注释和一些链接......
STL 容器使用许多不同的数据结构。map、set、multimap 和 multiset 通常被实现为具有红黑平衡规则的二叉树,例如,deque 可能(比知识更多)是数组中的循环队列,利用数组加倍或类似的增长模式.
标准实际上没有定义任何数据结构 - 但指定的性能特征显着限制了选择。
通常,您包含的数据直接包含在数据结构节点中,这些节点(默认情况下)保存在堆分配的内存中。您可以通过在指定容器时提供分配器模板参数来覆盖节点的内存源 - 稍后会详细介绍。如果您需要容器节点来引用(不包含)您的项目,请将指针或智能指针类型指定为包含类型。
例如,在 std::set 中,节点将是二叉树节点,其中包含用于 int 和两个子指针的空间,以及库所需的元数据(例如红/黑标志)。二叉树节点不会在您的应用程序地址空间中移动,因此您可以根据需要将指向数据项的指针存储在其他地方,但这不适用于所有容器 - 例如,向量中的插入将所有项移动到插入上方向上指向一个,并且可能必须重新分配整个向量,移动所有项目。
容器类实例通常非常小——通常只有几个指针。例如,std::set 等通常有一个根指针、一个指向最低键节点的指针和一个指向最高键节点的指针,可能还有更多元数据。
STL 面临的一个问题是在多项目节点中创建和销毁实例而不创建/销毁节点。例如,这发生在 std::vector 和 std::deque 中。严格来说,我不知道 STL 是如何做到的——但显而易见的方法需要放置新的和显式的析构函数调用。
Placement new 允许您在已分配的内存中创建对象。它基本上为您调用构造函数。它可以接受参数,因此它可以调用复制构造函数或其他构造函数,而不仅仅是默认构造函数。
要进行破坏,您可以通过(正确键入的)指针显式调用析构函数。
((mytype*) (void*) x)->~mytype ();
如果您没有声明显式构造函数,甚至对于不需要破坏的内置类型(如“int”),这将有效。
同样,要从一个构造实例分配给另一个实例,您可以显式调用 operator=。
基本上,容器能够相当容易地在现有节点中创建、复制和销毁数据,并且在需要时,元数据会跟踪当前在节点中构建的项目 - 例如 size() 指示当前在 std:: 中构建哪些项目:向量 - 可能有额外的非构造项,取决于当前容量()。
编辑- STL 可以通过(直接或有效地)使用 std::swap 而不是 operator= 来移动数据来优化。这在数据项是(例如)其他 STL 容器的情况下会很好,因此拥有大量引用的数据 - 交换可以避免大量复制。我不知道标准是否要求这样做,或者允许但不强制要求。但是,有一种众所周知的机制可以使用“特征”模板来做这种事情。默认的“特征”可以提供使用赋值的方法,而特定的覆盖可以通过使用交换方法来支持特殊情况类型。只要它是有效且可破坏的,抽象将是一个您不关心源中剩余的内容(原始数据,来自目标的数据,等等)的举动。
当然,在二叉树节点中,不需要这样做,因为每个节点只有一个项目,并且它总是被构造的。
剩下的问题是如何在节点结构中保留正确对齐和大小正确的空间以保存未知类型(指定为模板参数),而不会在创建/销毁节点时调用不需要的构造函数/析构函数。这在 C++0x 中会变得更容易,因为联合将能够保存非 POD 类型,从而提供方便的未初始化空间类型。在那之前,有一系列技巧或多或少地适用于不同程度的可移植性,毫无疑问,一个好的 STL 实现是一个值得学习的好例子。
就个人而言,我的容器使用空格换类型模板类。它使用特定于编译器的分配检查来确定编译时的对齐方式,并使用一些模板技巧从正确大小的字符数组、短数组、长数组等中进行选择。使用“#if defined”等选择了不可移植的对齐检查技巧,当有人向它抛出 128 位对齐要求时,模板将失败(在编译时),因为我还不允许这样做。
如何实际分配节点?好吧,大多数(全部?)STL 容器都采用“分配器”参数,默认为“分配器”。该标准实现从堆中获取内存并将其释放到堆中。实现正确的接口,它可以用自定义分配器替换。
这样做是我不喜欢做的事情,当然我的办公桌上没有 Stroustrups “The C++ Programming Language”。在您的分配器类中需要满足很多要求,至少在过去(情况可能有所改善),编译器错误消息没有帮助。
谷歌说你可以看这里,虽然......
我没有这本书,但根据它的描述,http://www.amazon.com/C-Standard-Template-Library/dp/0134376331包括
- 使用和实现组件的实用技术
这不是你想要的吗?