类型转换与空间配置器

类型转换

概念
  • C 语言中的类型转换:
    1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败;
    2. 显式类型转化:需要用户自己判断从什么类型转换为什么类型,转换格式为在需要转换的变量前加(要转换的类型)
  • 为什么需要 C++ 中的类型转换:
    1. 隐式类型转化有些情况下可能会出问题:比如数据精度丢失;
    2. 显式类型转换将所有情况混合在一起,代码不够清晰;
    3. 因此 C++ 提出了自己的类型转化风格,注意因为 C++ 要兼容 C 语言,所以 C++ 中还可以使用 C 语言的转化风格;
C++ 的四种转换
  • static_caststatic_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换;
int main(){
	double d = 12.34;
	int a = static_cast<int>(d);
	cout<<a<<endl;
	return0;
}
  • reinterpret_castreinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型,主要功能为:reinterpret_cast用在任意指针(或引用)类型之间的转换、以及指针与足够大的整数类型之间的转换、从整数类型(包括枚举类型)到无视大小的指针类型;
//可行的用法
typedef void (* FUNC)();
int DoSomething (int i){
	cout<<"DoSomething" <<endl;
	return 0;
}
void Test (){
	// reinterpret_cast允许编译器以FUNC的定义方式去看待DoSomething函数,所以非常的BUG,下面转换函数指针的代码是不可移植的,所以不建议这样用
	// C++不保证所有的函数指针都被一样的使用,所以这样用有时会产生不确定的结果
	FUNC f = reinterpret_cast< FUNC>(DoSomething );
	f();
}

//错误的使用
int main(){
	typedef int (*FunctionPointer)(int);
	int value = 21;
	FunctionPointer funcP;
	funcP = reinterpret_cast<FunctionPointer> (&value);
	funcP(value);
}
  • const_castconst_cast最常用的用途就是删除变量的const属性,方便赋值;
void Test (){
	const int a = 2;
	int* p = const_cast< int*>(&a);
	*p = 3;
	cout<<a <<endl;
}
  • dynamic_castdynamic_cast用于将一个父类对象的指针 / 引用转换为子类对象的指针 / 引用(动态转换);
    向上转型:子类对象指针/引用 -> 父类指针/引用,这是切片操作,不需要转换,赋值兼容规则;
    向下转型:父类对象指针/引用 -> 子类指针/引用,用dynamic_cast转型是安全的,也可以使用static_cast来进行转换,但是这是不安全的;
    • 注意:
      1. dynamic_cast只能用于含有虚函数的类;
      2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回 0;
      3. 下面的代码中可以看出dynamic_cast进行转换是安全的,static_cast进行转换是不安全的,所以当dynamic_cast进行转换时是会类型检查的,在父类向子类准换时,如果父类是用子类切片创建的,那么转换是安全的,如果父类是用自身创建的,那么dynamic_cast会失败,而static_cast成功;
class B{
public:
	virtual int fun(){}
};
class C : public B{
	//
};
int main(){
	B* pb1 = new B;
	C* pc1 = dynamic_cast<C*>(pb1); // 转换结果是无效的,安全的
	C* pc2 = static_cast<C*>(pb1);  // 转换结果是有效的,不安全

	B* pb2 = new C;
	pc1 = dynamic_cast<C*>(pb2);  // 转换结果是有效的,安全的
	pc2 = static_cast<C*>(pb2);  // 转换结果是有效的,安全的
}

空间配置器

概念
  • 空间配置器,顾名思义就是为各个容器高效的管理空间 (空间的申请与回收) 的,在默默地工作,虽然在常规使用 STL 时,可能用不到它,但站在学习研究的角度,学习它的实现原理对我们有很大的帮助;
作用
  • 前面在自己模拟实现vectorlistmapunordered_map等容器时,所有需要空间的地方都是通过new申请的,虽然代码可以正常运行,但是有以下不足之处:
    1. 空间申请与释放需要用户自己管理,容易造成内存泄漏;
    2. 频繁向系统申请小块内存块,容易造成内存碎片;
    3. 频繁向系统申请小块内存,影响程序运行效率;
    4. 直接使用mallocnew进行申请,每块空间都有额外空间浪费;
    5. 当申请空间失败时,我们无法很好地处理;
    6. 代码结构比较混乱,代码复用率不高;
    7. 未考虑线程安全问题;
  • 在面对上面的问题时,我们可以使用空间配置器很好的解决,它是一种非常高效的内存管理机制,下面我们来看看空间配置器的原理吧!
原理
分类
  • SGI-STL 以 128 字节大小作为小块内存与大块内存的分界线,将空间配置器其分为两级结构,一级空间配置器处理大块内存(>= 128 字节),二级空间配置器处理小块内存(< 128 字节);
一级空间配置器
  • 一级空间配置器原理非常简单,直接对mallocfree进行了封装,并增加了 C++ 中 set_new_handler 思想,底层代码我们就不看了,主要来介绍一下具体步骤;
  • 先使用malloc函数申请空间,申请成功则直接返回,申请失败则交由oom_malloc来处理;
  • 交由oom_malloc来处理时,先检测用户是否设置了空间不足的对应措施;
    • 如果没有设置对应措施,则抛异常;
    • 如果设置了对应措施,则执行用户的对应措施,然后再次使用malloc申请空间;
二级空间配置器
  • 二级空间配置器专门负责处理小于 128 字节的小块内存,如何才能提升小块内存的申请与释放呢?SGISTL 采用了内存池的技术来提高申请空间的速度以及减少额外空间的浪费,采用哈希桶的方式来提高用户获取空间的速度与高效管理;
    在这里插入图片描述
  • 先进行前期处理,判断所需内存是否小于 128 字节;
    • 大于 128:交由一级空间配置器申请;
    • 小于 128:交由二级空间配置器申请;
  • 如果用户所需内存不是 8 的整数倍,向上对齐到 8 的整数倍,假设此时所需的空间为 size;
  • 根据所需内存 size 的大小,找到哈希表中对应的桶位置,判断该桶中是否存储了内存块;
    • 有内存块:直接将桶中第一块内存返回给用户,然后结束
    • 无内存块:则需要向内存池申请空间;
  • 向内存池申请空间要分两种情况讨论:
    • 内存池剩余空间足够:
      • 只申请了一块 size 大小的内存,直接将该块返回给用户,然后结束
      • 申请了多块 size 大小的内存,将一块内存返回给用户,剩余的挂在对应的哈希桶中,然后结束
    • 内存池剩余空间不够用户所需,如果此时内存池中还有剩余内存,需要先将内存池中剩余的内存挂到对应的哈希桶中,然后调用malloc向系统申请空间:
  • 调用malloc向系统申请空间有两种情况:
    • 申请成功,则将内存放入内存池,然后重新向内存池申请空间,然后结束
    • 申请失败,则从比 size 更大的哈希桶中找可以利用的内存:
      • 如果找到更大的哈希桶,则将内存放入内存池中,然后重新向内存池申请空间,然后结束
      • 如果没有找到则调用一级空间配置器申请空间;
  • 调用一级空间配置器申请空间分两种情况:
    • 申请失败则抛异常,然后结束
    • 申请成功,则将内存放入内存池中,然后重新向内存池申请空间,然后结束

代码

一级空间配置器
template <int inst>
class __malloc_alloc_template{
private:
	static void *oom_malloc(size_t);
public:
	// 对malloc的封装
	static void * allocate(size_t n){
	// 申请空间成功,直接返回,失败交由oom_malloc处理
		void *result = malloc(n);
		if (0 == result)
			result = oom_malloc(n);
		return result;
	}
	// 对free的封装
	static void deallocate(void *p, size_t /* n */)
	{ free(p);}
	// 模拟set_new_handle
	// 该函数的参数为函数指针,返回值类型也为函数指针
	// void (* set_malloc_handler( void (*f)() ) )()
	static void (* set_malloc_handler(void (*f)()))(){
		void (* old)() = __malloc_alloc_oom_handler;
		__malloc_alloc_oom_handler = f;
		return(old);
	}
};
// malloc申请空间失败时代用该函数
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n){
	void (* my_malloc_handler)();
	void *result;
	for (;;){
		// 检测用户是否设置空间不足应对措施,如果没有设置,抛异常,模式new的方式
		my_malloc_handler = __malloc_alloc_oom_handler;
		if (0 == my_malloc_handler){
			__THROW_BAD_ALLOC;
		}
		// 如果设置,执行用户提供的空间不足应对措施
		(*my_malloc_handler)();
		// 继续申请空间,可能就会申请成功
		result = malloc(n);
		if (result)
			return(result);
	}
}
typedef __malloc_alloc_template<0> malloc_alloc;
二级空间配置器
前期准备
// 去掉代码中繁琐的部分
template <int inst>
class __default_alloc_template{
private:
	enum {__ALIGN = 8}; // 如果用户所需内存不是8的整数倍,向上对齐到8的整数倍
	enum {__MAX_BYTES = 128}; // 大小内存块的分界线
	enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; // 采用哈希桶保存小块内存时所需桶的个数
	// 如果用户所需内存块不是8的整数倍,向上对齐到8的整数倍
	static size_t ROUND_UP(size_t bytes){
		return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
	}
private:
	// 用联合体来维护链表结构
	union obj{
		union obj * free_list_link;
		char client_data[1]; /* The client sees this. */
	};
private:
	static obj * free_list[__NFREELISTS];
	// 哈希函数,根据用户提供字节数找到对应的桶号
	static size_t FREELIST_INDEX(size_t bytes){
		return (((bytes) + __ALIGN-1)/__ALIGN - 1);
	}
	// start_free与end_free用来标记内存池中大块内存的起始与末尾位置
	static char *start_free;
	static char *end_free;
	// 用来记录该空间配置器已经想系统索要了多少的内存块
	static size_t heap_size;
	// ...
} ;
申请空间
// 函数功能:向空间配置器索要空间
// 参数n: 用户所需空间字节数
// 返回值:返回空间的首地址
static void * allocate(size_t n){
	obj * __VOLATILE * my_free_list;
	obj * __RESTRICT result;
	// 检测用户所需空间释放超过128(即是否为小块内存)
	if (n > (size_t) __MAX_BYTES){
		// 不是小块内存交由一级空间配置器处理
		return (malloc_alloc::allocate(n));
	}
	// 根据用户所需字节找到对应的桶号
	my_free_list = free_list + FREELIST_INDEX(n);
	result = *my_free_list;
	// 如果该桶中没有内存块时,向该桶中补充空间
	if (result == 0){
		// 将n向上对齐到8的整数被,保证向桶中补充内存块时,内存块一定是8的整数倍
		void *r = refill(ROUND_UP(n));
		return r;
	}
	// 维护桶中剩余内存块的链式关系
	*my_free_list = result -> free_list_link;
	return (result);
}
填充内存块
// 函数功能:向哈希桶中补充空间
// 参数n:小块内存字节数
// 返回值:首个小块内存的首地址
template <int inst>
void* __default_alloc_template<inst>::refill(size_t n){
	// 一次性向内存池索要20个n字节的小块内存
	int nobjs = 20;
	char * chunk = chunk_alloc(n, nobjs);
	obj ** my_free_list;
	obj *result;
	obj *current_obj, *next_obj;
	int i;
	// 如果只要了一块,直接返回给用户使用
	if (1 == nobjs)
		return(chunk);
	// 找到对应的桶号
	my_free_list = free_list + FREELIST_INDEX(n);
	// 将第一块返回值用户,其他块连接在对应的桶中
	// 注:此处代码逻辑比较简单,但标准库实现稍微有点复杂,同学们可以自己实现
	result = (obj *)chunk;
	*my_free_list = next_obj = (obj *)(chunk + n);
	for (i = 1; ; i++){
		current_obj = next_obj;
		next_obj = (obj *)((char *)next_obj + n);
		if (nobjs - 1 == i){
			current_obj -> free_list_link = 0;
			break;
		}
		else{
			current_obj -> free_list_link = next_obj;
		}
	}
	return(result);
}
向内存池中索要空间
template <int inst>
char* __default_alloc_template<inst>::chunk_alloc(size_t size, int& nobjs){
	// 计算nobjs个size字节内存块的总大小以及内存池中剩余空间总大小
	char * result;
	size_t total_bytes = size * nobjs;
	size_t bytes_left = end_free - start_free;
	// 如果内存池可以提供total_bytes字节,返回
	if (bytes_left >= total_bytes){
		result = start_free;
		start_free += total_bytes;
		return(result);
	}
	else if (bytes_left >= size){
		// nobjs块无法提供,但是至少可以提供1块size字节内存块,提供后返回
		nobjs = bytes_left/size;
		total_bytes = size * nobjs;
		result = start_free;
		start_free += total_bytes;
		return(result);
	}
	else{
		// 内存池空间不足,连一块小块村内都不能提供
		// 向系统堆求助,往内存池中补充空间
		// 计算向内存中补充空间大小:本次空间总大小两倍 + 向系统申请总大小/16
		size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
		// 如果内存池有剩余空间(该空间一定是8的整数倍),将该空间挂到对应哈希桶中
		if (bytes_left > 0){
			// 找对用哈希桶,将剩余空间挂在其上
			obj ** my_free_list = free_list + FREELIST_INDEX(bytes_left);
			((obj *)start_free) -> free_list_link = *my_free_list;
			*my_ree_list = (obj *)start_free;
		}
		// 通过系统堆向内存池补充空间,如果补充成功,递归继续分配
		start_free = (char *)malloc(bytes_to_get);
		if (0 == start_free){
			// 通过系统堆补充空间失败,在哈希桶中找是否有没有使用的较大的内存块
			int i;
			obj ** my_free_list, *p;
			for (i = size; i <= __MAX_BYTES; i += __ALIGN){
				my_free_list = free_list + FREELIST_INDEX(i);
				p = *my_free_list;
				// 如果有,将该内存块补充进内存池,递归继续分配
				if (0 != p){
					*my_free_list = p -> free_list_link;
					start_free = (char *)p;
					end_free = start_free + i;
					return(chunk_alloc(size, nobjs));
				}
			}
			// 山穷水尽,只能向一级空间配置器求助
			// 注意:此处一定要将end_free置空,因为一级空间配置器一旦抛异常就会出问题
			end_free = 0;
			start_free = (char *)malloc_alloc::allocate(bytes_to_get);
		}
		// 通过系统堆向内存池补充空间成功,更新信息并继续分配
		heap_size += bytes_to_get;
		end_free = start_free + bytes_to_get;
		return(chunk_alloc(size, nobjs));
	}
}
空间回收
// 函数功能:用户将空间归还给空间配置器
// 参数:p空间首地址 n空间总大小
static void deallocate(void *p, size_t n){
	obj *q = (obj *)p;
	obj ** my_free_list;
	// 如果空间不是小块内存,交给一级空间配置器回收
	if (n > (size_t) __MAX_BYTES){
		malloc_alloc::deallocate(p, n);
		return;
	}
	// 找到对应的哈希桶,将内存挂在哈希桶中
	my_free_list = free_list + FREELIST_INDEX(n);
	q -> free_list_link = *my_free_list;
	*my_free_list = q;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值