重要网页
www.cplusplus.com www.cppreference.com www.gcc.gnu.org
STL的六大部件
1)容器(Containers):存放我们要操作的数据,可以是数字、对象等; 2)分配器(Allocators):容器需要占用内存,容器占用的内存由分配器分配; 3)算法(Algorithms):被独立出来的模板函数,用来操作容器,包括常见的排序算法、查找算法等; 4)迭代器(Iterators):算法既然要操作容器中的数据,需要有工具访问容器中数据,那就是迭代器,是一种泛化的指针; 5)适配器(Adapters):一些容器底层和数据操作具有一定的相似,所以一些容器使用其他容器作为底层数据结构, 将其他容器的函数转换为自己的函数; 6)仿函数(Functors):实际上是类中的operator()小括号运算符重载函数,存在类似函数的行为。
容器结构分析
容器测试
1、array(数组)
#include <iostream>
#include <array>
#include <ctime>
#include <algorithm>
#include <cstdlib>
#define use_sequence_find 1
#define use_qsort_bserach_find 1
namespace test_array {
/*
CompareLong是作为qsort、bsearch的比较函数
*/
int CompareLong(void const* l, void const* r) {
return *(long*)l < *(long*)r;
}
void test() {
long target = 23456;
clock_t curT;
const long ARRAY_SIZE = 100000;
srand(time(0));
std::array<long, ARRAY_SIZE> myArray;
curT = clock();
for (long i = 0; i < ARRAY_SIZE; ++i) {
myArray[i] = rand();
}
std::cout << "insert element use time " << clock() - curT << std::endl;
/*
使用顺序查找find对容器元素进行查找
*/
#if use_sequence_find
curT = clock();
auto iter = std::find(myArray.begin(), myArray.end(), target);
std::cout << "find element use time " << clock() - curT << std::endl;
if (iter != myArray.end())
std::cout << "find target " << target << " index in " << iter - myArray.begin() << std::endl;
else
std::cout << "find target " << target << " error!" << std::endl;
#endif
/*
使用<stdlib>的库函数qsort、bsearch函数对容器元素进行排序和查找
*/
#if use_qsort_bserach_find
curT = clock();
qsort(myArray.data(), ARRAY_SIZE, sizeof(long), CompareLong);
std::cout << "qsort use time " << clock() - curT << std::endl;
curT = clock();
bsearch(&target, myArray.data(), ARRAY_SIZE, sizeof(long), CompareLong);
std::cout << "bsearch use time " << clock() - curT << std::endl;
#endif
}
}
控制台输出结果如下:
insert element use time 13
find target 23456 index in 5723
qsort use time 9
bsearch use time 0
find element use time 0
请按任意键继续. . .
2、vector(可扩展数组)
#include <iostream>
#include <vector>
#include <ctime>
#include <algorithm>
#include <string>
#include <cstdlib>
namespace test_vector {
int CompareString(void const* l, void const* r) {
return *(std::string const*)l < *(std::string const*)r;
}
void test() {
clock_t curT;
std::string target = "23456";
srand(time(0));
const long VECTOR_SIZE = 100000;
std::vector<std::string> myVec;
char buf[10] = { '\0' };
curT = clock();
for (long i = 0; i < VECTOR_SIZE; ++i) {
try {
sprintf_s(buf, 10, "%d", rand());
myVec.push_back(std::string(buf));
} catch (std::exception& p) { // 当内存不够分配的情况下,会抛出bad_alloc异常
std::cout << "i=" << i << " " << p.what() << std::endl;
abort(); //退出程序
}
}
std::cout << "insert element use time " << clock() - curT << std::endl;
#define use_sequence_find 1
#define use_sort_bsearch_find 1
#define use_sort_find 1
#define use_qsort_find 0
#if use_sequence_find
/*
使用顺序遍历find查找元素
*/
curT = clock();
auto iter = std::find(myVec.begin(), myVec.end(), target);
std::cout << "find target " << target << " use time " << clock() - curT << std::endl;
#endif
#if use_sort_bsearch_find
/*
使用sort、bsearch查找元素
*/
#if use_sort_find
curT = clock();
std::sort(myVec.begin(), myVec.end());
std::cout << "sort use time " << clock() - curT << std::endl;
#endif
#if use_qsort_find
curT = clock();
qsort(myVec.data(), myVec.size(), sizeof(std::string), CompareString);
std::cout << "qsort use time " << clock() - curT << std::endl;
#endif
curT = clock();
bsearch(&target, myVec.data(), myVec.size(), sizeof(std::string), CompareString);
std::cout << "bsearch use time " << clock() - curT << std::endl;
#endif
}
}
当使用qsort函数对vector进行排序时,控制台输出信息如下:
insert element use time 1550
find target 23456 use time 10
qsort use time 135
bsearch use time 0
请按任意键继续. . .
当使用sort函数对vector进行排序时,控制台输出信息如下:
insert element use time 1510
find target 23456 use time 4
sort use time 3585
bsearch use time 0
请按任意键继续. . .
根据上述两种排序函数的用时发现一个很有趣的东西,qsort排序的性能优于sort排序大约20倍(已测试多次结果均如此,而且数据量越大比较越明显),这种性能提升是非常优越的!所以我建议排序查找尽量使用qsort+bsearch的方式。
3、list(双向链表)
#include <iostream>
#include <list>
#include <ctime>
#include <string>
namespace test_list {
void test() {
clock_t curT;
std::string target = "23456";
srand(time(0));
const long LIST_SIZE = 100000;
std::list<std::string> myList;
char buf[10] = { '\0' };
curT = clock();
for (long i = 0; i < LIST_SIZE; ++i) {
try {
sprintf_s(buf, 10, "%d", rand());
myList.push_back(std::string(buf));
}
catch (std::exception& p) {
std::cout << "i=" << i << " " << p.what() << std::endl;
abort();
}
}
std::cout << "insert element use time " << clock() - curT << std::endl;
/*
使用顺序遍历find查找元素
*/
curT = clock();
auto iter = std::find(myList.begin(), myList.end(), target);
std::cout << "find target " << target << " use time " << clock() - curT << std::endl;
/*
使用list的成员函数sort来排序list
(我们不能使用algorithm的sort函数对list进行排序,\
因为在algorithm的sort函数里面用到了iterator的加减乘除操作,\
但是在list数据结构中,这些泛化指针不是连续的)
*/
curT = clock();
myList.sort();
std::cout << "sort use time " << clock() - curT << std::endl;
}
}
使用list容器特别注意的一点就是不能使用algorithm提供的sort函数对list容器进行排序,而是直接使用容器类内置sort函数进行排序!
4、forward_list(单向链表)
#include <iostream>
#include <forward_list>
#include <ctime>
#include <string>
namespace test_forward_list {
void test() {
clock_t curT;
std::string target = "23456";
srand(time(0));
const long LIST_SIZE = 100000;
std::forward_list<std::string> myList;
char buf[10] = { '\0' };
curT = clock();
for (long i = 0; i < LIST_SIZE; ++i) {
try {
sprintf_s(buf, 10, "%d", rand());
myList.push_front(std::string(buf));
}
catch (std::exception& p) {
std::cout << "i=" << i << " " << p.what() << std::endl;
abort();
}
}
std::cout << "insert element use time " << clock() - curT << std::endl;
/*
使用顺序遍历find查找元素
*/
curT = clock();
auto iter = std::find(myList.begin(), myList.end(), target);
std::cout << "find target " << target << " use time " << clock() - curT << std::endl;
/*
使用forward_list容器类的成员函数sort来排序forward_list
*/
curT = clock();
myList.sort();
std::cout << "sort use time " << clock() - curT << std::endl;
}
}
forward_list容器的使用和list容器使用类似,不同之处在于forward_list是单向的,故而在插入/删除元素的时候只能在首端插入/删除元素。
5、deque(双端队列)
#include <iostream>
#include <deque>
#include <ctime>
#include <algorithm>
#include <string>
namespace test_deque {
void test() {
clock_t curT;
std::string target = "23456";
srand(time(0));
const long DEQUE_SIZE = 100000;
std::deque<std::string> myDeque;
char buf[10] = { '\0' };
curT = clock();
for (long i = 0; i < DEQUE_SIZE; ++i) {
try {
sprintf_s(buf, 10, "%d", rand());
myDeque.push_back(std::string(buf));
}
catch (std::exception& p) {
std::cout << "i=" << i << " " << p.what() << std::endl;
abort();
}
}
std::cout << "insert element use time " << clock() - curT << std::endl;
/*
使用顺序遍历find查找元素
*/
curT = clock();
auto iter = std::find(myDeque.begin(), myDeque.end(), target);
std::cout << "find target " << target << " use time " << clock() - curT << std::endl;
/*
使用algorithm的sort函数对deque容器进行排序
*/
curT = clock();
sort(myDeque.begin(), myDeque.end());
std::cout << "sort use time " << clock() - curT << std::endl;
}
}
6、stack(栈)
#include <iostream>
#include <stack>
#include <ctime>
#include <string>
namespace test_stack {
void test() {
clock_t curT;
std::string target = "23456";
srand(time(0));
const long STACK_SIZE = 100000;
std::stack<std::string> myStack;
char buf[10] = { '\0' };
curT = clock();
for (long i = 0; i < STACK_SIZE; ++i) {
try {
sprintf_s(buf, 10, "%d", rand());
myStack.push(std::string(buf));
}
catch (std::exception& p) {
std::cout << "i=" << i << " " << p.what() << std::endl;
abort();
}
}
std::cout << "insert element use time " << clock() - curT << std::endl;
}
}
stack是一种特殊的容器适配器,其实现内部是默认持有一个deque,并且保持元素先进后出的特性,因此stack容器内不存在迭代器(Iterators),故而我们不可以去排序一个stack。
7、queue(队列)
#include <iostream>
#include <queue>
#include <ctime>
#include <string>
namespace test_queue {
void test() {
clock_t curT;
std::string target = "23456";
srand(time(0));
const long STACK_SIZE = 100000;
std::queue<std::string> myQueue;
char buf[10] = { '\0' };
curT = clock();
for (long i = 0; i < STACK_SIZE; ++i) {
try {
sprintf_s(buf, 10, "%d", rand());
myQueue.push(std::string(buf));
}
catch (std::exception& p) {
std::cout << "i=" << i << " " << p.what() << std::endl;
abort();
}
}
std::cout << "insert element use time " << clock() - curT << std::endl;
}
}
queue是一种特殊的容器适配器,其实现内部是默认持有一个deque,并且保持元素先进先出的特性,因此queue容器内不存在迭代器(Iterators),故而我们不可以去排序一个queue。
8、multiset
#include <set>
#include <iostream>
#include <ctime>
#include <string>
namespace test_multiset {
void test() {
clock_t curT;
std::string target = "23456";
srand(time(0));
const long MULTISET_SIZE = 100000;
std::multiset<std::string> myMultiset;
char buf[10] = { '\0' };
curT = clock();
for (long i = 0; i < MULTISET_SIZE; ++i) {
try {
sprintf_s(buf, 10, "%d", rand());
myMultiset.insert(std::string(buf));
}
catch (std::exception& p) {
std::cout << "i=" << i << " " << p.what() << std::endl;
abort();
}
}
std::cout << "insert element use time " << clock() - curT << std::endl;
#define use_set_find 1
#define use_algorithm_find 0
/*
使用set容器类成员函数查找目标值
*/
#if use_set_find
curT = clock();
myMultiset.find(target);
std::cout << "set find target " << target << " use time " << clock() - curT << std::endl;
#endif
/*
使用algorithm提供的find查找目标值
*/
#if use_algorithm_find
curT = clock();
std::find(myMultiset.begin(), myMultiset.end(), target);
std::cout << "algorithm find target " << target << " use time " << clock() - curT << std::endl;
#endif
}
}
9、multimap
#include <map>
#include <iostream>
#include <ctime>
#include <string>
namespace test_multimap {
void test() {
clock_t curT;
std::string target = "23456";
srand(time(0));
const long MUTIMAP_SIZE = 100000;
std::multimap<std::string, long> myMultimap;
char buf[10] = { '\0' };
curT = clock();
for (long i = 0; i < MUTIMAP_SIZE; ++i) {
try {
sprintf_s(buf, 10, "%d", rand());
myMultimap.insert(std::pair<std::string, long>(std::string(buf), i));
}
catch (std::exception& p) {
std::cout << "i=" << i << " " << p.what() << std::endl;
abort();
}
}
std::cout << "insert element use time " << clock() - curT << std::endl;
/*
multimap查找key下的所有value的三种方法
*/
std::cout << "use find() + count() ... " << std::endl;
#if 1 // 使用find结合count
int num = myMultimap.count(target);
auto iter = myMultimap.find(target);
for (int i = 0; i < num; ++i,++iter){
std::cout << "key = " << iter->first << " ; value = " << iter->second << std::endl;
}
#endif
std::cout << "use equal_range() ... " << std::endl;
#if 1 // 使用equal_range函数
auto pair = myMultimap.equal_range(target);
while (pair.first != pair.second) {
std::cout << "key = " << pair.first->first << " ; value = " << pair.first->second << std::endl;
pair.first++;
}
#endif
std::cout << "use lower_bound() + upper_bound() ... " << std::endl;
#if 1 // 使用lower_bound和upper_bound函数
auto beg = myMultimap.lower_bound(target);
auto end = myMultimap.upper_bound(target);
while (beg != end) {
std::cout << "key = " << beg->first << " ; value = " << beg->second << std::endl;
beg++;
}
#endif
}
}
multimap查找元素的三种方法: 1)find() + count() 2)equal_range() 3)low_bound() + upper_bound()
10、set
#include <set>
#include <iostream>
#include <ctime>
#include <string>
namespace test_set {
void test() {
clock_t curT;
std::string target = "23456";
srand(time(0));
const long SET_SIZE = 100000;
std::set<std::string> mySet;
char buf[10] = { '\0' };
curT = clock();
for (long i = 0; i < SET_SIZE; ++i) {
try {
sprintf_s(buf, 10, "%d", rand());
mySet.insert(std::string(buf));
}
catch (std::exception& p) {
std::cout << "i=" << i << " " << p.what() << std::endl;
abort();
}
}
std::cout << "insert element use time " << clock() - curT << std::endl;
#define use_set_find 1
#define use_algorithm_find 0
/*
使用set容器类成员函数查找目标值
*/
#if use_set_find
curT = clock();
mySet.find(target);
std::cout << "set find target " << target << " use time " << clock() - curT << std::endl;
#endif
/*
使用algorithm提供的find查找目标值
*/
#if use_algorithm_find
curT = clock();
std::find(mySet.begin(), mySet.end(), target);
std::cout << "algorithm find target " << target << " use time " << clock() - curT << std::endl;
#endif
}
}
根据多次试验发现使用容器set的成员函数find比algorithm的find函数的查找速度要快。 此外,multiset和set的区别在于:multiset允许元素重复,而set不允许元素重复。
11、map
#include <map>
#include <iostream>
#include <ctime>
#include <string>
namespace test_map {
void test() {
clock_t curT;
std::string target = "23456";
srand(time(0));
const long MAP_SIZE = 100000;
std::map<std::string, long> myMap;
char buf[10] = { '\0' };
curT = clock();
for (long i = 0; i < MAP_SIZE; ++i) {
try {
sprintf_s(buf, 10, "%d", rand());
myMap.insert(std::pair<std::string, long>(std::string(buf), i));
}
catch (std::exception& p) {
std::cout << "i=" << i << " " << p.what() << std::endl;
abort();
}
}
std::cout << "insert element use time " << clock() - curT << std::endl;
#define use_map_find 1
#define use_algorithm_find 1
/*
使用map容器类成员函数查找目标值
*/
#if use_map_find
curT = clock();
myMap.find(target);
std::cout << "map find target " << target << " use time " << clock() - curT << std::endl;
#endif
/*
使用algorithm提供的find查找目标值
*/
#if use_algorithm_find
curT = clock();
std::find(myMap.begin(), myMap.end(), target);
std::cout << "algorithm find target " << target << " use time " << clock() - curT << std::endl;
#endif
}
}
map不允许使用algorithm的find函数! 此外,multimap和map的区别在于:multimap允许元素键key重复,而map不允许元素键key重复; 当map在insert过程中遇到重复的key时,此时的操作是忽略而不是替换。
12、unordered_multiset
#include <unordered_set>
#include <iostream>
#include <ctime>
#include <string>
namespace test_unordered_multiset {
void test() {
clock_t curT;
std::string target = "23456";
srand(time(0));
const long UNODEREDMULTISET_SIZE = 100000;
std::unordered_multiset<std::string> myUnorderedMultiSet;
char buf[10] = { '\0' };
curT = clock();
for (long i = 0; i < UNODEREDMULTISET_SIZE; ++i) {
try {
sprintf_s(buf, 10, "%d", rand());
myUnorderedMultiSet.insert(std::string(buf));
}
catch (std::exception& p) {
std::cout << "i=" << i << " " << p.what() << std::endl;
abort();
}
}
std::cout << "insert element use time " << clock() - curT << std::endl;
#define use_unordered_multiset_find 1
#define use_algorithm_find 1
/*
使用unordered_multiset容器类成员函数查找目标值
*/
#if use_unordered_multiset_find
curT = clock();
myUnorderedMultiSet.find(target);
std::cout << "set find target " << target << " use time " << clock() - curT << std::endl;
#endif
/*
使用algorithm提供的find查找目标值
*/
#if use_algorithm_find
curT = clock();
std::find(myUnorderedMultiSet.begin(), myUnorderedMultiSet.end(), target);
std::cout << "algorithm find target " << target << " use time " << clock() - curT << std::endl;
#endif
}
}
13、unordered_multimap
#include <unordered_map>
#include <iostream>
#include <ctime>
#include <string>
namespace test_unordered_multimap {
void test() {
clock_t curT;
std::string target = "23456";
srand(time(0));
const long UNORDEREDMULTIMAP_SIZE = 100000;
std::unordered_multimap<std::string, long> myUnorderedMultiMap;
char buf[10] = { '\0' };
curT = clock();
for (long i = 0; i < UNORDEREDMULTIMAP_SIZE; ++i) {
try {
sprintf_s(buf, 10, "%d", rand());
myUnorderedMultiMap.insert(std::pair<std::string, long>(std::string(buf), i));
}
catch (std::exception& p) {
std::cout << "i=" << i << " " << p.what() << std::endl;
abort();
}
}
std::cout << "insert element use time " << clock() - curT << std::endl;
/*
使用unordered_multimap容器类成员函数查找目标值
*/
#define use_unordered_multimap_find 1
#if use_unordered_multimap_find
curT = clock();
myUnorderedMultiMap.find(target);
std::cout << "map find target " << target << " use time " << clock() - curT << std::endl;
#endif
}
}
14、unordered_set
#include <unordered_set>
#include <iostream>
#include <ctime>
#include <string>
namespace test_unordered_set {
void test() {
clock_t curT;
std::string target = "23456";
srand(time(0));
const long UNOREDEREDSET_SIZE = 100000;
std::unordered_set<std::string> myUnorderedSet;
char buf[10] = { '\0' };
curT = clock();
for (long i = 0; i < UNOREDEREDSET_SIZE; ++i) {
try {
sprintf_s(buf, 10, "%d", rand());
myUnorderedSet.insert(std::string(buf));
}
catch (std::exception& p) {
std::cout << "i=" << i << " " << p.what() << std::endl;
abort();
}
}
std::cout << "insert element use time " << clock() - curT << std::endl;
#define use_unordered_set_find 1
#define use_algorithm_find 1
/*
使用unordered_set容器类成员函数查找目标值
*/
#if use_unordered_set_find
curT = clock();
myUnorderedSet.find(target);
std::cout << "unordered_set find target " << target << " use time " << clock() - curT << std::endl;
#endif
/*
使用algorithm提供的find查找目标值
*/
#if use_algorithm_find
curT = clock();
std::find(myUnorderedSet.begin(), myUnorderedSet.end(), target);
std::cout << "algorithm find target " << target << " use time " << clock() - curT << std::endl;
#endif
}
}
15、unordered_map
#include <unordered_map>
#include <iostream>
#include <ctime>
#include <string>
namespace test_unordered_map {
void test() {
clock_t curT;
std::string target = "23456";
srand(time(0));
const long UNORDEREDMAP_SIZE = 100000;
std::unordered_map<std::string, long> myUnorderedMap;
char buf[10] = { '\0' };
curT = clock();
for (long i = 0; i < UNORDEREDMAP_SIZE; ++i) {
try {
sprintf_s(buf, 10, "%d", rand());
myUnorderedMap.insert(std::pair<std::string, long>(std::string(buf), i));
}
catch (std::exception& p) {
std::cout << "i=" << i << " " << p.what() << std::endl;
abort();
}
}
std::cout << "insert element use time " << clock() - curT << std::endl;
/*
使用map容器类成员函数查找目标值
*/
#define use_map_find 1
#if use_map_find
curT = clock();
myUnorderedMap.find(target);
std::cout << "myUnorderedMap find target " << target << " use time " << clock() - curT << std::endl;
#endif
}
}
模型的泛化和特化
空间配置器Allocators
1、vc++、CB适配器:allocator
调用了operartor new和operator delete,底层调用的是malloc和free,除了需要存储数据的内存还需要额外的开销(cookie表示元素大小,消耗8个字节),因此不适合小内存的分配
2、gcc\g++ alloc配置器:
设计了双级配置器: 1)一级分配器:当申请内存大小 size > 128bytes,直接启动一级适配器,直接调用malloc 和 free 2)二级分配器:当申请内存大小 < 128bytes时,就启动第二级分配器,从一个预先分配好的内存池中取一块内存交付给用户,这个内存池由16个不同大小(8的倍数,8~128byte)的空闲列表组成,allocator会根据申请内存的大小(将这个大小round up成8的倍数)从对应的空闲块列表取表头块给用户。这种做法有两个优点: a、小对象的快速分配。小对象是从内存池分配的,这个内存池是系统调用一次malloc分配一块足够大的区域给程序备用,当内存池耗尽时再向系统申请一块新的区域,整个过程类似于批发和零售,起先是由allocator向总经商批发一定量的货物,然后零售给用户,与每次都总经商要一个货物再零售给用户的过程相比,显然是快捷了。当然,这里的一个问题时,内存池会带来一些内存的浪费,比如当只需分配一个小对象时,为了这个小对象可能要申请一大块的内存池,但这个浪费还是值得的,况且这种情况在实际应用中也并不多见。 b、避免了内存碎片的生成。程序中的小对象的分配极易造成内存碎片,给操作系统的内存管理带来了很大压力,系统中碎片的增多不但会影响内存分配的速度,而且会极大地降低内存的利用率。以内存池组织小对象的内存,从系统的角度看,只是一大块内存池,看不到小对象内存的分配和释放。
3、alloc两级配置器实现
1)Level-1_alloc 一级配置器实现:
template <class T>
class malloc_alloc {
private:
static coom_handler_type oom_handler;
static T* oom_malloc(size_t) {
coom_handler_type my_malloc_handler;
void *result;
for (;;)
{
my_malloc_handler = oom_handler;
if (0 == my_malloc_handler)
{
std::cerr << "out of memory" << std::endl;
exit(1);
}
(*my_malloc_handler)();
result = malloc(n);
if (result)
{
return(result);
}
}
};
static T* oom_realloc(T*, size_t) {
coom_handler_type my_malloc_handler;
void *result;
for (;;)
{
my_malloc_handler = oom_handler;
if (0 == my_malloc_handler)
{
std::cerr << "out of memory" << std::endl;
exit(1);
}
(*my_malloc_handler)();
result = realloc((char*)p, n);
if (result)
{
return(result);
}
}
};
public:
// allocate成员函数用来分配内存
static T* allocate(size_t n) {
// 直接使用<stdlib.h>的库函数malloc分配内存
T* res = (T*)malloc(n);
// 如果分配内存失败,则调用OOM(out of memory)处理
if (res == nullptr) {
res = oom_malloc(n);
}
return res;
};
// deallocate成员函数用来释放内存
static void deallocate(T* p) {
// 直接使用<stdlib.h>的库函数free释放内存
free((void*)p);
};
// reallocate成员函数用来重新分配内存
static T* reallocate(T* p, size_t new_size) {
// 直接使用<stdlib.h>的库函数realloc重新分配内存
T* res = realloc((void*)p, new_size);
// 如果重新分配内存失败,则调用OOM(out of memory)处理
if (res == nullptr) {
res = oom_realloc(p, new_size);
}
return res;
};
// 设置函数指针
static coom_handler_type set_malloc_handler(coom_handler_type f)
{
coom_handler_type old = oom_handler;
oom_handler = f;
return(old);
}
};
// 静态变量初始化
template <class T>
coom_handler_type malloc_alloc<T>::oom_handler = (coom_handler_type)0;
2)Level-2_alloc 二级配置器实现:
//内存对齐最小字节
enum { __ALIGN = 8 };
// 内存池中最大内存节点 128
enum { __MAX_BYTES = 128 };
// 内存
enum { __NFREELISTS = __MAX_BYTES / __ALIGN };
// 空闲块列表节点
union cnode_alloc_obj
{
union cnode_alloc_obj* free_list_link;
char client_data[1]; // info address
};
template <class T>
class default_alloc {
typedef cnode_alloc_obj obj;
private:
// 内存字节对齐 8的倍数
static size_t round_up(size_t bytes)
{
return ((bytes + __ALIGN - 1) &~ (__ALIGN - 1));
}
// 内存字节数组的下标
static size_t free_list_index(size_t bytes)
{
return ((bytes + __ALIGN - 1) / __ALIGN - 1);
}
// 创建空闲链表并返回第一个可用内存块地址,并将链表下一个结点地址挂在空闲块列表表头数组上
static T* refill(size_t n) {
// 预设置一个一次性分配小内存的空闲块数nobjs
int nobjs = 20, i;
// 通过chunk_alloc函数传入nobjs引用,返回大内存的首地址(nobjs会随着空闲内存大小改变)
char* chunk = chunk_alloc(n, nobjs);
obj** my_free_list;
obj* result, *current_obj, *next_obj;
// 如果块数为1,则直接返回空闲块内存地址
if (1 == nobjs)
return (T*)chunk;
// 若空闲块数不为1,返回第一块空闲内存的地址,并将后续的空闲块链接起来
my_free_list = free_list + free_list_index(n);
result = (obj*)chunk;
*my_free_list = next_obj = (obj*)(chunk + n);
while (nobjs-- > 2) {
current_obj = next_obj;
next_obj = (obj*)((char*)current_obj + n);
current_obj->free_list_link = next_obj;
}
next_obj->free_list_link = nullptr;
return (T*)result;
};
// 根据传入引用参数nobjs以及自身空闲内存调整分配内存大小
static char* chunk_alloc(size_t size, int & nobjes) {
char* res;
// 所需内存大小
size_t total_bytes = size * nobjs;
// 自身剩余空闲内存大小
size_t bytes_left = end_free - start_free;
// 若空闲内存大小大于所需内存大小,则直接返回空闲内存首地址,并移动start_free指针
if (bytes_left >= total_bytes) {
res = start_free;
start_free += total_bytes;
return res;
}
// 若空闲内存不能满足 nobjs 个 size 大小的内存,但是至少能满足一个或多个 size,则动态修改 nobjs,并返回空闲内存首地址,并移动start_free指针
else if (bytes_left >= size) {
nobjs = bytes_left / size;
total_bytes = size * nobjs;
res = start_free;
start_free += total_bytes;
return res;
}
// 若空闲内存不能满足一个 size 大小的内存,则需要另外申请一块大内存
else {
// 计算所需内存大小数(一般是 total_bytes 的两倍 + 附加量)
// 附加量 :一个随着配置次数增加而愈来愈大的附加量
// 加这个值的作用是起到一个附加值的作用,毕竟我们扩容如果只是简单的扩容到需求的两倍不够灵活
// 倘若加上这个附加量,这个附加量的值是根据原先内存池的大小右移位4位来生成的,就会更加灵活
size_t bytes_to_get = 2 * total_bytes + round_up(heap_size >> 4);
// 如果自身剩余空闲内存还有空间,则将这些空间放置空闲块链表当中
if (bytes_left > 0) {
obj** my_free_list = free_list + free_list_index(bytes_left);
((obj*)start_free)->free_list_link = *my_free_list;
*my_free_list = (obj *)start_free;
}
// 另外找一块大内存重新移动start_free和end_free指针
start_free = (char *)malloc_alloc::allocate(bytes_to_get);
// 如果分配失败了的话,我们还可以去比size大的空闲块链表找找有没有空闲块,如果有也可以返回这一块空闲块地址。
if (start_free == nullptr) {
int i;
obj** my_free_list, *p;
// 从 size 开始遍历至 _MAX_BYTES
for (i = size; i <= _MAX_BYTES; I += _ALIGN) {
my_free_list = free_list + free_list_index(i);
p = *my_free_list;
// 如果存在空闲块,则移动start_free和end_free至这块空闲块的首尾地址,并再次调用chunk_alloc即可获得一块size大小的空闲块
if (p != nullptr) {
*my_free_list = p->free_list_link;
start_free = (char*)p;
end_free = start_free + i;
return chunk_alloc(size, nobjs);
}
}
end_free = 0;
//这一步就是垂死挣扎,希望第一级配置器可以通过OOM机制尽力
start_free = (char*)malloc_alloc::allocate(bytes_to_get);
}
// 增加heap_size,移动end_free指针
heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
return chunk_alloc(size, nobjs);
}
};
// 内存池空闲起始地址
static char* start_free;
// 内存池空闲末尾地址
static char* end_free;
// 内存池大小
static size_t heap_size;
// 16个空闲块列表表头的数组
static obj* free_list[__NFREELISTS];
// 已用内存大小
static size_t m_use_size;
public:
static T* allocate(size_t n) {
obj** my_free_list;
obj* result;
// 增加使用内存
m_use_size += n;
// 比较申请内存的大小,若 n 大于 128,则直接使用一级分配器分配内存
if (n > __MAX_BYTES) {
return malloc_alloc<T>::allocate(n);
}
my_free_list = free_list + free_list_index(n);
result = *my_free_list;
if (result == nullptr) {
T *res = refill(round_up(n));
return res;
}
*my_free_list = result->free_list_link;
return (T*)result;
};
static void deallocate(T* p, size_t n) {
obj *q = (obj *)p;
obj ** my_free_list;
m_use_size -= n;
if (n > (size_t)__MAX_BYTES) {
malloc_alloc::deallocate(p, n);
return;
}
my_free_list = free_list + free_list_index(n);
q->free_list_link = *my_free_list;
*my_free_list = q;
}
};
/*
静态变量初始化
*/
template <class T>
char* default_alloc<T>::start_free = (char*)0;
template <class T>
char* default_alloc<T>::end_free = (char*)0;
template <class T>
size_t default_alloc<T>::heap_size = (size_t)0;
template <class T>
default_alloc<T>::obj* default_alloc<T>::free_list[__NFREELISTS] = { nullptr };
template <class T>
size_t default_alloc<T>::m_use_size = (size_t)0;
二级分配器allocators分配内存算法讲解如下: a、算法:allocate 输入:申请内存的大小size 输出:若分配成功,则返回一个内存的地址,否则返回NULL { if(size大于128){ 启动第一级分配器直接调用malloc分配所需的内存并返回内存地址;} else { 将size向上round up成8的倍数并根据大小从free_list中取对应的表头free_list_head; if(free_list_head不为空){ 从该列表中取下第一个空闲块并调整free_list; 返回free_list_head; } else { 调用refill算法建立空闲块列表并返回所需的内存地址; } } } b、算法: refill 输入:内存块的大小size 输出:建立空闲块链表并返回第一个可用的内存块地址 { 调用chunk_alloc算法分配若干个大小为size的连续内存区域并返回起始地址chunk和成功分配的块数nobj; if(块数为1)直接返回chunk; 否则 { 开始在chunk地址块中建立free_list; 根据size取free_list中对应的表头元素free_list_head; 将free_list_head指向chunk中偏移起始地址为size的地址处, 即free_list_head=(obj*)(chunk+size); 再将整个chunk中剩下的nobj-1个内存块串联起来构成一个空闲列表; 返回chunk,即chunk中第一块空闲的内存块; } } c、算法:chunk_alloc 输入:内存块的大小size,预分配的内存块块数nobj(以引用传递) 输出:一块连续的内存区域的地址和该区域内可以容纳的内存块的块数 { 计算总共所需的内存大小total_bytes; if(内存池中足以分配,即end_free - start_free >= total_bytes) { 则更新start_free; 返回旧的start_free; } else if(内存池中不够分配nobj个内存块,但至少可以分配一个){ 计算可以分配的内存块数并修改nobj; 更新start_free并返回原来的start_free; } else { //内存池连一块内存块都分配不了 先将内存池的内存块链入到对应的free_list中后; 调用malloc操作重新分配内存池,大小为2倍的total_bytes加附加量,start_free指向返回的内存地址; if(分配不成功) { if(16个空闲列表中尚有空闲块) 尝试将16个空闲列表中空闲块回收到内存池中再调用chunk_alloc(size, nobj); else { 调用第一级分配器尝试out of memory机制是否还有用; } } 更新end_free为start_free+total_bytes,heap_size为2倍的total_bytes; 调用chunk_alloc(size,nobj); } } d、算法:deallocate 输入:需要释放的内存块地址p和大小size { if(size大于128字节)直接调用free(p)释放; else{ 将size向上取8的倍数,并据此获取对应的空闲列表表头指针free_list_head; 调整free_list_head将p链入空闲列表块中; } }
list源码分析
GNU2.9版本
1)SGI STL中list是使用环状双向链表实现的。它的结点结构定义如下:
/*
list_node节点设计了一个前驱prev和后继next,list内部实现一个双向环形链表
*/
template <class T>
struct __list_node {
typedef void* void_pointer;
void_pointer next;
void_pointer prev;
T data;
};
2)list的定义比较简单,而且环状双向链表的操作并不用过多的考虑边界条件。list创建的时候会创建一个空白的结点指针,并用其node成员指向它,下面是list的基本定义:
template <class T, class Alloc = alloc>
class list {
protected:
typedef void* void_pointer;
typedef __list_node<T> list_node;
typedef simple_alloc<list_node, Alloc> list_node_allocator;
public:
typedef T value_type;
typedef value_type* pointer;
typedef value_type& reference;
typedef list_node* link_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
public:
// 定义迭代器类型
typedef __list_iterator<T, T&, T*> iterator;
protected:
link_type node; // 空白结点 链表尾结点
// ... 一些基本操作
};
list环状双向链表示意图: 根据示意图我们很容易理解list的构造:
template <class T, class Alloc = alloc>
class list {
public:
// ...
// 可以从图中直观的看出来
iterator begin() { return (link_type)((*node).next); }
iterator end() { return node; }
// 默认构造函数
list() { empty_initialize(); }
protected:
// 为结点分配内存
link_type get_node() { return list_node_allocator::allocate(); }
// 回收内存
void put_node(link_type p) { list_node_allocator::deallocate(p); }
// 构造node
link_type create_node(const T& x) {
link_type p = get_node();
construct(&p->data, x);
return p;
}
// 销毁node
void destroy_node(link_type p) {
destroy(&p->data);
put_node(p);
}
// 初始化
void empty_initialize() {
node = get_node();
node->next = node;
node->prev = node;
}
};
list成员函数的实现其实就是对环状双向链表的操作。 首先是insert、erase、transfer的实现,关于插入删除大部分都调用这三个函数,实际上就是改变结点pre跟next指针的指向。
iterator insert(iterator position, const T& x) {
link_type tmp = create_node(x);
// 改变四个指针的指向 实际就是双向链表元素的插入
tmp->next = position.node;
tmp->prev = position.node->prev;
(link_type(position.node->prev))->next = tmp;
position.node->prev = tmp;
return tmp;
}
iterator erase(iterator position) {
// 改变四个指针的指向 实际就是双向链表的元素删除
link_type next_node = link_type(position.node->next);
link_type prev_node = link_type(position.node->prev);
prev_node->next = next_node;
next_node->prev = prev_node;
destroy_node(position.node);
return iterator(next_node);
}
// 将[first, last)插入到position位置(可以是同一个链表)
void transfer(iterator position, iterator first, iterator last) {
if (position != last) {
// 实际上也是改变双向链表结点指针的指向 具体操作看下图
(*(link_type((*last.node).prev))).next = position.node;
(*(link_type((*first.node).prev))).next = last.node;
(*(link_type((*position.node).prev))).next = first.node;
link_type tmp = link_type((*position.node).prev);
(*position.node).prev = (*last.node).prev;
(*last.node).prev = (*first.node).prev;
(*first.node).prev = tmp;
}
}
有了上面3个函数,list对外的接口实现就非常简单了:
void push_front(const T& x) { insert(begin(), x); }
void push_back(const T& x) { insert(end(), x); }
void pop_front() { erase(begin()); }
void pop_back() {
iterator tmp = end();
erase(--tmp);
}
// splice有很多重载版本
void splice(iterator position, list&, iterator first, iterator last) {
if (first != last)
transfer(position, first, last);
}
// merge函数实现跟归并排序中合并的操作类似
template <class T, class Alloc>
void list<T, Alloc>::merge(list<T, Alloc>& x) { ... }
// reserse函数每次都调用transfer将结点插入到begin()之前
template <class T, class Alloc>
void list<T, Alloc>::reverse() {
if (node->next == node || link_type(node->next)->next == node) return;
iterator first = begin();
++first;
while (first != end()) {
iterator old = first;
++first;
transfer(begin(), old, first);
}
}
// list必须使用自己的sort()成员函数 因为STL算法中的sort()只接受RamdonAccessIterator
// 该函数采用的是quick sort
template <class T, class Alloc>
void list<T, Alloc>::sort() { ... }