智能指针简单实现
文章目录
- 智能指针简单实现
- 1、实现变量自动清理
- 2、内存管理析构函数实现
- 3、小结
我们知道,在C语言中,对内存进行管理是一件非常麻烦的事情,一不小心就可能出现内存泄露或导致一些不可知道的错误。本文将详细介绍如何利用GCC编译器的特性来实现一个C语言智能指针。
1、实现变量自动清理
在GCC中,可以通过cleanup
属性来实现变量进行自动清理。只需创建某种可能的类型属性,以使变量在超出范围后释放自身:
#define autofree __attribute__((cleanup(free_stack)))
__attribute__ ((always_inline))
inline void free_stack(void *ptr) {free(*(void **) ptr);printf("cleanup done\n");
}
用法非常简单:
// cleanup.c
int main(int argc, char** argv) {printf("hello world,auto clean up\n");autofree int *i = malloc(sizeof (int));*i = 1;return 0;
}
编译并运行:
gcc -o cleanup cleanup.c
输出如下:
hello world,auto clean up
cleanup
2、内存管理析构函数实现
在前面的变量自动清理方法在简单的变量内存管理中效果比较好。在实际应用中,我们知道大多数动态分配的数据都是复杂的数据,具有(也是动态分配的)成员 ,并且由于无法在结构成员上添加清理属性,因此有必要引入某种**析构函数机制
**。
在这里,我们在分配的内存中添加一些元数据 - 这应该是实现新目标的最非侵入性的方式:
我们将使用两个函数:一个用于分配内存,另一个用于释放内存。
/*** @brief 内存分配函数* @param size 内存分配大小* @param dtor 析构函数
*/
__attribute__((malloc))
void* smalloc(size_t size, void (*dtor)(void*)) {struct meta* meta = malloc(sizeof(struct meta) + size);*meta = (struct meta){.dtor = dtor,.ptr = meta + 1};return meta->ptr;
}/*** @brief 内存释放函数* @param ptr 需要释放的内存对象
*/
void sfree(void* ptr) {if (ptr == NULL)return;struct meta* meta = get_meta(ptr);assert(ptr == meta->ptr); // ptr shall be a pointer returned by smallocmeta->dtor(ptr);free(meta);
}
这两个函数都非常简单:smalloc
分配内存来托管请求的数据大小和我们需要的元数据。 然后,它初始化所述元数据并存储析构函数,并将指针返回到未初始化的用户数据的开头。
sfree
的行为与 free 完全相同,因为如果传递 NULL,它不会执行任何操作,否则会释放内存。 唯一的区别是,它在实际释放之前调用在调用smalloc
期间存储的析构函数
,以便可以执行清理步骤。
#define smart __attribute__((cleanup(sfree_stack)))
__attribute__ ((always_inline))
inline void sfree_stack(void *ptr) {sfree(*(void **) ptr);
}
sfree
运行析构函数的直接后果之一是 sfree
是通用释放器,类似于 C++ 中的 delete
关键字。
这意味着对于smalloc
管理的任何类型,我们都可以对其调用 sfree
,而不必担心真正的析构函数,而且我知道它会被销毁 - 这是一个巨大的改进。
使用示例如下:
// smalloc.c#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>#define autofree __attribute__((cleanup(free_stack)))
__attribute__((always_inline))
inline void free_stack(void* ptr) {printf("cleanup\n");free(*(void**)ptr);
}struct meta {void (*dtor)(void*);void* ptr;
};static struct meta* get_meta(void* ptr) {return ptr - sizeof(struct meta);
}/*** @brief 内存分配函数* @param size 内存分配大小* @param dtor 析构函数
*/
__attribute__((malloc))
void* smalloc(size_t size, void (*dtor)(void*)) {struct meta* meta = malloc(sizeof(struct meta) + size);*meta = (struct meta){.dtor = dtor,.ptr = meta + 1};return meta->ptr;
}/*** @brief 内存释放函数* @param ptr 需要释放的内存对象
*/
void sfree(void* ptr) {if (ptr == NULL)return;struct meta* meta = get_meta(ptr);assert(ptr == meta->ptr); // ptr shall be a pointer returned by smallocmeta->dtor(ptr);free(meta);
}#define smart __attribute__((cleanup(sfree_stack)))
__attribute__((always_inline))
inline void sfree_stack(void* ptr) {sfree(*(void**)ptr);
}
typedef struct _values {int key;int32_t* int_values;
}values_t;typedef struct {uint8_t* datas;values_t* values;
}complex_data_t;void complex_data_free(complex_data_t* data) {if (data == NULL) {return;}if (data->values) {if (data->values->int_values) {free(data->values->int_values);data->values->int_values = NULL;printf("complex_data_free:free int_values done\n");}free(data->values);data->values = NULL;printf("complex_data_free:free values done\n");}free(data->datas);data->datas = NULL;printf("complex_data_free:done\n");
}int main(int argc, char** argv) {printf("hello world,auto clean up\n");complex_data_t* datas = (complex_data_t*)smalloc(sizeof(complex_data_t), complex_data_free);memset(datas,0,sizeof(complex_data_t));if(datas){datas->datas = malloc(sizeof(char) * 32);datas->values = malloc(sizeof(values_t));if(datas->values){memset(datas->values,0,sizeof(values_t));datas->values->int_values = malloc(sizeof(int32_t));if(datas->values->int_values){memset(datas->values->int_values,0,sizeof(int32_t));}}}sfree(datas);return 0;
}
编译:
gcc -o smalloc smalloc.c
运行结果如下:
hello world,auto clean up
complex_data_free:free int_values done
complex_data_free:free values done
complex_data_free:done
这里的一个缺点是,即使对于简单类型,我们也必须指定一个有效的析构函数,而这对于简单类型来说并不总是可取的,因此允许 NULL 作为一个有效参数,这意味着没有析构函数。
3、小结
在本文中,我们通过使用GCC编译的属性实现了变量和内存管理的简单实现,在实际的智能指针实现中是非常复杂的,例如,unique_ptr
,shared_ptr
,以及对象引用计数等等这些实现复杂的。