libcsptr是一个使用c语言实现的简单的智能指针库
github仓库地址是:[libcsptr]https://github.com/proficientc/libcsptr

数据结构

libcsptr中的智能指针的数据结构分布大致如图,从图中可以看出,每个智能指针都保存有元数据,其中包含引用计算以及析构函数等信息

元数据

libcsptr中的ptr智能指针都有一个元数据的结构体,对于unique_ptr来说,这个结构体是s_meta。对于shared_ptr来说,这个结构体是s_meta_shared
这两个结构体的组成是:

s_meta ->   pointer_kind     (指针类型,数据类型:enum)
            f_destructor     (析构函数,类型:void*(void* , void*))
            void * ptr       (有效数据起始地址)

s_meta_shared  ->  pointer_kind              (enum)
                   f_destructor              (析构函数,类型:void*(void* , void*))
                   void * ptr                (有效数据起始地址)
                   volatile size_t ref_count (引用计数)

指针参数

libcsptr在开辟智能指针时需要一个参数结构体,s_malloc_args

typedef struct {
    CSPTR_SENTINEL_DEC
    size_t size;                  // 有效数据大小
    size_t nmemb;                 // 成员个数,用于判断是否是数组
    enum pointer_kind kind;       // 智能指针类型
    f_destructor dtor;            //   析构函数指针
    struct {
        const void *data;         //    meta中的data地址
        size_t size;              //    meta中的data的大小
    } meta;
} s_smalloc_args;

s_malloc_args 用在函数s_malloc_impl中,下面是s_malloc_impl的注释

CSPTR_MALLOC_API
static void *smalloc_impl(s_smalloc_args *args) {
    if (!args->size)
        return NULL;

    // align the sizes to the size of a word
    size_t aligned_metasize = align(args->meta.size);    //meta.size
    size_t size = align(args->size);                     //args->size  有效数据大小

    size_t head_size = args->kind & SHARED ? sizeof (s_meta_shared) : sizeof (s_meta);  // s_meta结构体大小
    s_meta_shared *ptr = alloc_entry(head_size, size, aligned_metasize);                // s_meta结构体大小 + meta data的大小 + 有效数据的大小 + 存放s_meta结构体大小与meta data的大小之和的数据大小
    if (ptr == NULL)
        return NULL;

    char *shifted = (char *) ptr + head_size;             // meta data数据起始地址
    if (args->meta.size && args->meta.data)
        memcpy(shifted, args->meta.data, args->meta.size);

    size_t *sz = (size_t *) (shifted + aligned_metasize);  //sz 指向存放放s_meta结构体大小与meta data的大小之和的数据的位置
    *sz = head_size + aligned_metasize;

    *(s_meta*) ptr = (s_meta) {
        .kind = args->kind,
        .dtor = args->dtor,
#ifndef NDEBUG
        .ptr = sz + 1                                     // sz再加1就是有效数据起始地址
#endif
    };  

    if (args->kind & SHARED)
        ptr->ref_count = 1;

    return sz + 1;
}

meta data的作用

从dealloc_entry函数大概可以看出,meta data中的数据最后会被dtor析构函数释放

CSPTR_INLINE static void dealloc_entry(s_meta *meta, void *ptr) {
    if (meta->dtor) {
        void *user_meta = get_smart_ptr_meta(ptr);
        if (meta->kind & ARRAY) {
            s_meta_array *arr_meta = (void *) (meta + 1);
            for (size_t i = 0; i < arr_meta->nmemb; ++i)
                meta->dtor((char *) ptr + arr_meta->size * i, user_meta);
        } else
            meta->dtor(ptr, user_meta);       //user_meta 指向meta data起始地址
    }

#ifdef SMALLOC_FIXED_ALLOCATOR
    free(meta);
#else /* !SMALLOC_FIXED_ALLOCATOR */
    smalloc_allocator.dealloc(meta);
#endif /* !SMALLOC_FIXED_ALLOCATOR */
}

可以看个例子:

check/test/shared.c中的dtor其实只是个对dtor_run赋下值的函数,也就是说对于一般最简单的shared_ptr来说,meta data没什么用

START_TEST (test_shared_sref) {
    int dtor_run = 0;          
    f_destructor dtor = lambda(void, (UNUSED void *ptr, UNUSED void *meta) { dtor_run = 1; }); 
    smart void *ptr = shared_ptr(int, 42, dtor);
    assert_valid_ptr(ptr);     

    {
        smart void *ptr2 = sref(ptr);   
        ck_assert_msg(ptr == ptr2, "Expected reference to be the same pointer.");
    }
    ck_assert_msg(dtor_run == 0, "Expected destructor NOT to have run.");
} END_TEST