TBOX封装了一套跨平台的异常捕获实现,来模拟windows的seh异常处理功能,而且是线程安全的。
这个就不用多说了,在vs下直接用 __try、__except 关键字就行了,如果在mingw下编译, 通过 setjmp实现也很方便。
注: 由于使用setjmp 进行寄存器现场保护, 如果使用整型局部变量, 有可能会被编译器优化到寄存器中。 所以try内部的修改,可能会在异常捕获后,被会恢复掉。 最好加上volatile来禁止优化。
__tb_volatile__ tb_size_t i = 0;
__tb_try
{
i++;
// 捕获段错误
*((__tb_volatile__ tb_size_t*)0) = 0;
// 捕获除0错误
// __tb_volatile__ tb_size_t a = 0; a /= a;
}
__tb_except(1)
{
// __tb_except(1): 处理异常
// __tb_except(0): 路由异常到外层, 支持嵌套处理
}
__tb_end
有些平台异常捕获是被禁用的,所以如果确实想要使用这种异常捕获机制,首先得确保对应平台下的配置文件plat/xxx/config.h
定义了TB_CONFIG_EXCEPTION_ENABLE
这个宏,然后重新编译才行。
虽然tbox对异常支持的挺完善了,但是个人还是不建议太过频繁的使用异常捕获。
TBOX的内存管理模型,参考了linux kernel的内存管理机制,并在其基础上做了一些改进和优化。
整个内存分配的最底层,都是基于large_pool的大块内存分配池,类似于linux的基于page的分配管理,不过有所不同的是,large_pool并没有像linux那样使用buddy算法进行(2^N)*page进行分配,这样如果需要2.1m的内存,需要分配4m的内存块,这样力度太大,非常浪费。
因此large_pool内部采用N*page的基于page_size为最小粒度进行分配,因此每次分配顶多浪费不到一页的空间。
而且如果需要的内存不到整页,剩下的内存也会一并返回给上层,如果上层需要(比如small_pool),可以充分利用这多余的部分内存空间,使得内存利用率达到最优化。
而且根据tb_init实际传入的参数需求,large_pool有两种模式:
具体使用哪种方式,根据应用需求,一般的应用只需要使用方式1就行了,这个时候tb_init传tb_null就行了,如果是嵌入式应用,需要管理有限的一块内存空间,这个时候可以使用方式2, tb_init传入指定内存空间地址和大小。
这里就主要看下方式2的large_pool的内存结构(假设页大小是4KB):
--------------------------------------------------------------------------
| data |
--------------------------------------------------------------------------
|
--------------------------------------------------------------------------
| head | 4KB | 16KB | 8KB | 128KB | ... | 32KB | ... | 4KB*N |
--------------------------------------------------------------------------
TBOX的内存分配在调试模式下,可以检测支持内存泄露和越界,而且还能精确定位到出问题的那块内存具体分配位置,和函数调用堆栈。
内存泄露的检测必须在程序退出的前一刻,调用tb_exit()的时候,才会执行,如果有泄露,会有详细输出到终端上。
tb_void_t tb_demo_leak()
{
tb_pointer_t data = tb_malloc0(10);
}
输出:
[tbox]: [error]: leak: 0x7f9d5b058908 at tb_static_fixed_pool_dump(): 735, memory/impl/static_fixed_pool.c
[tbox]: [error]: data: from: tb_demo_leak(): 43, memory/check.c
[tbox]: [error]: [0x000001050e742a]: 0 demo.b 0x00000001050e742a tb_fixed_pool_malloc0_ + 186
[tbox]: [error]: [0x000001050f972b]: 1 demo.b 0x00000001050f972b tb_small_pool_malloc0_ + 507
[tbox]: [error]: [0x000001050f593c]: 2 demo.b 0x00000001050f593c tb_pool_malloc0_ + 540
[tbox]: [error]: [0x00000105063cd7]: 3 demo.b 0x0000000105063cd7 tb_demo_leak + 55
[tbox]: [error]: [0x00000105063e44]: 4 demo.b 0x0000000105063e44 tb_demo_memory_check_main + 20
[tbox]: [error]: [0x0000010505b08e]: 5 demo.b 0x000000010505b08e main + 878
[tbox]: [error]: [0x007fff8c95a5fd]: 6 libdyld.dylib 0x00007fff8c95a5fd start + 1
[tbox]: [error]: [0x00000000000002]: 7 ??? 0x0000000000000002 0x0 + 2
[tbox]: [error]: data: 0x7f9d5b058908, size: 10, patch: cc
stl的容器库常用模式就是将容器、迭代器和算法的进行分离,容器专于存储,迭代器负责枚举,这样互相独立好处多多。
因此TBOX也借鉴了这种模式,不同的是没用模板,仅仅用了c语言来实现。容器库里面的大部分容器都是继承自迭代器的,所以迭代起来相当的方便。
下面先看个迭代器使用的例子:
stl的容器库非常强大,但是为了要兼容各种元素类型,采用了模板进行泛化,这样的好处就是使用非常的方便,但是编译器会对使用到的每种类型都进行一遍实例化,用的类型太多的话不仅影响编译速度而且生成的可执行文件也很冗余。
因此,TBOX在设计容器架构的时候,引入tb_item_func_t
类型,来设置容器使用的成员类型,这样在实现容器通用性的同时,也不会产生过的冗余,而且容器接口操作上,同样相当的便利。
可以先看个简单使用哈希的例子:
/* 初始化hash, 哈希桶大小8
* 键:大小写敏感字符串
* 值:long整型
*/
tb_hash_map_ref_t hash = tb_hash_map_init(8, tb_item_func_str(tb_true), tb_item_func_long());
if (hash)
{
// 设置键值对:"key" => 123
tb_hash_map_set(hash, "key", (tb_pointer_t)123);
// 获取值
tb_long_t value = (tb_long_t)tb_hash_map_get(hash, "key");
// 退出hash
tb_hash_map_exit(hash);
}
/* 初始化hash, 哈希桶大小: TB_hash_map_BULK_SIZE_MICRO
* 键:tb_struct_xxxx_t 结构体类型,内存数据由hash内部自己维护, 后面两个参数设置成员的释放回调函数
* 值:true类型,永远是tb_true, 这种hash相当于stl的set<tb_struct_xxxx_t>,内部会去优化掉value占用的内存
*/
tb_hash_map_ref_t hash = tb_hash_map_init(TB_hash_map_BULK_SIZE_MICRO, tb_item_func_mem(sizeof(tb_struct_xxxx_t), tb_null, tb_null), tb_item_func_true());
if (hash)
{
// 初始化tb_struct_xxxx_t
tb_struct_xxxx_t xxxx = {0};
// 设置键值对:xxxx => tb_true
tb_hash_map_set(hash, &xxxx, (tb_pointer_t)tb_true);
// 判断键是否存在
if (tb_hash_map_get(hash, &xxxx))
{
// ...
}
// 退出hash
tb_hash_map_exit(hash);
}
/* 初始化hash, 哈希桶大小使用默认大小: 0
* 键:大小写不敏感字符串
* 值:uint8整型
*/
tb_hash_map_ref_t hash = tb_hash_map_init(0, tb_item_func_str(tb_false), tb_item_func_uint8());
if (hash)
{
// 设置键值对:"key" => 123
tb_hash_map_set(hash, "key", (tb_pointer_t)123);
// 获取u位整型键值
tb_uint8_t value = (tb_uint8_t)tb_hash_map_get(hash, "key");
// 退出hash
tb_hash_map_exit(hash);
}