按照官方推荐的源码阅读顺序,就先来看看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)
的主要作用:
不过我觉得这里最主要的作用是定义一个块,可以定义变量实现更复杂功能,可以使用像使用函数一样使用宏并且不会出错。可以考虑下面的宏定义的使用
#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(&s_free_cnt);
---
#define hatomic_inc ATOMIC_INC
#define hatomic_dec ATOMIC_DEC
---
#define ATOMIC_INC(p) ATOMIC_ADD(p, 1)
#define ATOMIC_DEC(p) ATOMIC_SUB(p, 1)
---
#define ATOMIC_ADD atomic_fetch_add
#define ATOMIC_SUB atomic_fetch_sub
可以通过hv_memcheck宏定义函数来打印相关信息:
HV_INLINE void hv_memcheck() {
printf("Memcheck => alloc:%ld free:%ld\n", hv_alloc_cnt(), hv_free_cnt());
}
内存统计函数由于使用了原子数,所以是线程安全的,可以写代码测试一下:
同步模式:
void async_test()
{
/*----- 内存分配函数 ------*/
for (int i = 0; i < 10000; ++i){
int* pa = (int*)safe_malloc(sizeof(int));
safe_free(pa);
}
// 统计内存使用情况
hv_memcheck();
}
...
int main()
{
async_test();
return 0;
}
---结果---
Memcheck => alloc:10000 free:10000
异步模式:
void* do_thread(void *)
{
/*----- 内存分配函数 ------*/
for (int i = 0; i < 100; ++i){
int* pa = (int*)safe_malloc(sizeof(int));
safe_free(pa);
}
}
void sync_test()
{
pthread_t ths[100];
for (unsigned long & th : ths){
pthread_create(&th, nullptr, do_thread, nullptr);
}
for (auto& th : ths){
pthread_join(th, nullptr);
}
// 统计内存使用情况
hv_memcheck();
}
...
int main()
{
async_test();
return 0;
}
---结果---
Memcheck => alloc:10000 free:10000
下面给出hbase.h
部分函数测试结果:
字符串函数测试:
void string_func_test()
{
// 字符串转大写测试
const char* base_str = "Hello LibHv -- 2021-6-30";
char base_strs[100]{0};
safe_strncpy(base_strs, base_str, sizeof(base_strs) - 1);
printf("---字符串反转---\n");
printf("%s <--> %s\n", base_str, strreverse(base_strs));
printf("---字符串转大写测试---\n");
printf("%s --> %s\n", base_str, (const char*)strupper(base_strs));
printf("---字符串转小写测试---\n");
printf("%s --> %s\n", base_str, (const char*)strlower(base_strs));
printf("---字符串拼接---\n");
safe_strncat(base_strs, "Hello Code!", sizeof(base_strs) - strlen(base_strs));
printf("base_str ++ --> %s\n", base_strs);
printf("---字符串判断---\n");
if (strstartswith(base_str, "Hello")){
printf("%s是以%s开头的\n", base_str, "Hello");
}
if (!strendswith(base_str, "Hello")){
printf("%s不是以%s结尾的\n", base_str, "Hello");
}
if (strcontains(base_str, "2021")){
printf("%s包含子串: %s\n", base_str, "2021");
}
}
---结果---
---字符串反转---
Hello LibHv -- 2021-6-30 <--> 03-6-1202 -- vHbiL olleH
---字符串转大写测试---
Hello LibHv -- 2021-6-30 --> 03-6-1202 -- VHBIL OLLEH
---字符串转小写测试---
Hello LibHv -- 2021-6-30 --> 03-6-1202 -- vhbil olleh
---字符串拼接---
base_str ++ --> 03-6-1202 -- vhbil ollehHello Code!
---字符串判断---
Hello LibHv -- 2021-6-30是以Hello开头的
Hello LibHv -- 2021-6-30不是以Hello结尾的
Hello LibHv -- 2021-6-30包含子串: 2021
文件路径相关函数:
void path_test()
{
const char* base_path = ".test/main.cpp";
char buf[MAX_PATH + 1];
printf("%s后缀名为:%s\n", base_path, hv_suffixname(base_path));
printf("%s 's basename: %s\n", base_path, hv_basename(base_path));
printf("当前可执行文件路径:%s\n", get_executable_path(buf, MAX_PATH));
printf("当前可执行文件父目录:%s\n", get_executable_dir(buf, MAX_PATH));
printf("当前可执行文件名称:%s\n", get_executable_file(buf, MAX_PATH));
}
---结果---
.test/main.cpp后缀名为:cpp
.test/main.cpp 's basename: main.cpp
当前可执行文件路径:/home/ticks/codes/Clion/libhv/base/test/cmake-build-debug/test
当前可执行文件父目录:/home/ticks/codes/Clion/libhv/base/test/cmake-build-debug
当前可执行文件名称:test