c语言RAII实现

Intro

RAII是c++的看手好戏,用过的都说好。面对c,可没有RAII这种东西使用。还好,gcc提供了一些折中的方案来剑走偏峰,来实现RAII。如下:

__attribute__((cleanup(free-function)))

attribute的使用简介还请看ref。此不在讲解范围内。

ref:
6.39 Attribute Syntax

具体用法

用这个实现自动回收str和tree;

/* Char Str free */
#define auto_sfree __attribute__ ((cleanup(sfree)))
#define auto_treefree __attribute__ ((cleanup(treefree)))
__attribute__((always_inline))
    inline void sfree(void* str) {
        if( !str ) return;
        free( *(void**)str );
    }
/* tree free */
__attribute__((always_inline))
    inline void treefree(void* tree) {
        if( !tree ) return;
        tree_free_por( *(treenode**)tree );
    }

usage:

int main(void) {
    auto_sfree tmp_str = calloc(100,1);
}
Smart Pointer in C

我们可以使用这个编译器提供的特性来实现一个c的smart pointer。

|------------------------|---------------------- // -----|
| metadata    | padding? | actual data                   |
|------------------------|---------------------- // -----|
^                        ^ returned address (word aligned)
 `- start of allocated
    block

/*
 *
 * 储存数据和dtor的函数指针
 */
struct meta_data {
    void (*dtor)(void*);
    void* data;
};


// 从data的位置往前移动到struct meta_data的开始位置
static struct meta_data* get_meta(void* ptr) {
    return ptr - sizeof(struct meta_data);
}


__attribute__((malloc))
void* smart_malloc(size_t size, void(*dtor)(void*)) {
    // 一次性分配好struct meta_data的大小和需要分配的大小
    struct meta_data* mdata = malloc(sizeof(struct meta_date) + size);

    // 初始化
    *mdata = (struct meta_data) {
        .dtor = dtor,
        .data = mdata + 1 // 加1实际上是 1 = sizeof( struct meta_data )
    };

    return mdata->data;
}


void smart_free(void* data) {
    if( data == NULL ) return;

    data = *(void**)data;
    if( data == NULL ) return;

    struct meta_data* mdata = get_meta(data);
    assert( data == mdata->data);

    if( mdata->dtor ) {
            mdata->dtor( data );
    }
    free(mdata);
}

// usage
#define auto_free __attribute__ ((cleanup(smart_free)))

void test_1( ) {
    // 当退出作用域之后会自动调用 smart_free(pint);
    auto_free int* pint = smart_malloc(sizeof(int), NULL);
    // 当然如果你的struct 之中是有ptr指向另外的地址需要释放,需要你自己定义destructor
    // 如果你理解C++ RAII这些概念应该非常好理解
    // malloc分配时,其原理也大致和这个差不多。
}

Advance Technique

从linux/list.h的container_of()看从get_meta()

getmeta的局限性是在抽象不够,只能获取metastruct的位置,而如果换个目标类型则不行。如果我们需要从struct member ptr 中获取我们指定类型的ptr返回地址。很明显这种写法不行。而Containerof则提供这样的能力。原本这是为了配套linux kernel定义的list实现,其基本思路还是和getmeta一样,只是containerof的设计更通用。详情可见container_of

struct mystruct {
		void* data;
		struct list_head mylist;
};

#define container_of(ptr, type, member) ({ \
                const typeof( ((type *)0)->member ) *__mptr = (ptr); 
                (type *)( (char *)__mptr - offsetof(type,member) );})
tring的一种实现

image
这种实现可以保证string对象和原始char* str大小一样。

Reference
  1. 6.39 Attribute Syntax

  2. Implementing smart pointers for the C programming language

  3. Kernel Data Structures Linkedlist

  4. container_of

posted @ 2021-11-23 00:12  uttep  阅读(468)  评论(0)    收藏  举报