hbase.h
注: 阅读源码时最新代码为 2021年6月更新
按照官方推荐的源码阅读顺序,就先来看看base模块。
base模块主要目录结构如下:
.
├── array.h 动态数组
├── hatomic.h 原子操作
├── hbase.h 基础函数
├── hbuf.h 缓存
├── hdef.h 常见宏定义
├── heap.h 堆
├── hendian.h 大小端
├── herr.h 错误码表
├── hlog.h 日志
├── hmath.h 数学函数
├── hmutex.h 线程同步锁
├── hplatform.h 平台相关宏
├── hproc.h 进程
├── hsocket.h 套接字
├── hssl.h SSL/TLS
├── hsysinfo.h 系统信息
├── hthread.h 线程
├── htime.h 时间
├── hversion.h 版本
├── list.h 链表
└── queue.h 队列
今天主要研究 hbase.h
,也就是libhv的基础函数。
打开 hbase.h
文件,第一眼便看到引入了3个头文件: hexport.h
hplaform.h
hdef.h
hexport.h
和hplaform.h
都是一些与平台有关的宏定义,可以大致看一下然后略过,这里主要看一下hdef.h
,里面有一些会经常用到的宏定义。
里面包含了一些常见函数,像 取绝对值、获取数组大小 、设置、清除和获取位、取最大值、最小值、介于中间的值、一些其他宏定义、安全分配和释放内存等。
在看分配内存的代码时让我感到惊讶,代码是这样的:
#define SAFE_ALLOC(p, size)\
do {\
void* ptr = malloc(size);\
if (!ptr) {\
fprintf(stderr, "malloc failed!\n");\
exit(-1);\
} \
memset(ptr, 0, size);\
*(void**)&(p) = ptr;\
} while(0)
#endif
这是一个宏定义,用于安全分配内存,先使用malloc分配内存,如果失败 exit 退出并输出错误信息,然后使用 memset 初始化,最后赋值给p。主要是这里的 do ... while(0)
,咋一看感觉是累赘,反正都是执行一次,为什么还要多此一举用循环呢,仔细一像既然这样用就一定有它的道理,便马上打开浏览器搜索了do … while(0)的用处。不看不知道,一看下一跳,原来这样写还有这么多用处!!!
这里结合百度到的说法总结一下 do ... while(0)
的主要作用:
- 有一个 {}块,可以进行变量定义,实现更复杂功能
- 结尾没有分号,可以更好衔接上下文(特别是有判断语句的上下文)
- 消除goto语句,使用break跳出循环
不过我觉得这里最主要的作用是定义一个块,可以定义变量实现更复杂功能,可以使用像使用函数一样使用宏并且不会出错。可以考虑下面的宏定义的使用
#define do_work() work1();work2();
#define safe_do_work() do{work1();work2();}while(0)
. . .
// 第一种
if (is_work)
do_work();
// 等价于 -->
if (is_work)
work1();work1();;
// 第二种
if (is_eork)
safe_do_work();
// 等价于 -->
if (is_work)
do{
work1();work2();}while(0);
这里显然第一种定义不仅不会达到预期效果,还会出现错误。
hbase.h
文件里面主要定义了 安全分配和释放内存的函数、C风格字符串操作函数、路径处理函数、以及关于可执行文件路径和目录相关函数。
其中内存分配函数使用了两个全局变量:s_alloc_cnt
s_free_cnt
定义如下:
static hatomic_t s_alloc_cnt = HATOMIC_VAR_INIT(0);
static hatomic_t s_free_cnt = HATOMIC_VAR_INIT(0);
...
typedef atomic_long hatomic_t;
这两个全局变量为原子类型,用于准确统计内存分配和释放情况:
分配内存时会调用hatomic_inc(&s_alloc_cnt);
来增加已分配内存数量
释放内存时会调用hatomic_inc(&s_free_cnt);
来增加已释放内存数量
这两个函数也是通过宏定义实现,具体实现会根据不同平台提供不同宏定义,大致如下:
hatomic_inc(&s_alloc_cnt);
hatomic_inc(&