当前位置: 首页 > 工具软件 > libhv > 使用案例 >

libhv源码学习 -- base模块 -- hbase.h

漆雕恺
2023-12-01

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.hhplaform.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(&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
 类似资料: