简述
什么是RAII
RAII(Resource Acquisition Is Initialization)是c++之父Bjarne Stroustrup提出的概念。资源一般分三个步骤:获取、使用和销毁,而在自由使用内存的c语言中,资源的销毁常常是程序员容易遗漏的事情,让程序自动销毁资源也成为了业界的常规方案。
c实现
/* 入参是析构函数,在malloc资源时也指定free要使用的函数,尽量不在宏中写函数,减少后续定位的复杂度,并且也满足malloc和free的配对出现,可读性更好 */
#define RAII_FREE(FreeFunc) __attribute__((cleanup(FreeFunc)))
以上代码使用了编译属性__attribute__,在函数退出后自动回收资源,使用方式如下:
- 示例一:释放内存
void MemFreeL2PointerAndSetNull(void *ptr)
{
void **pptr = (void **) ptr;
if (*pptr != NULL)
{
free(*pptr);
*pptr = NULL;
}
}
void TestFunc()
{
RAII_FREE(MemFreeL2PointerAndSetNull) TestObj *obj = (TestObj *)malloc(sizeof(TestObj));
obj->grade = 1;
}
- 示例二:关闭文件
void CloseFile(void *ptr)
{
void **pptr = (void **) ptr;
if (*pptr != NULL)
{
(void) fclose(*pptr);
}
}
void test_whenOpenFile_thenCloseFile(const char *flePath)
{
RAII_FREE(CloseFile) FILE *handle = fopen(flePath, "r");
if (!handle)
{
printf("open file filed");
}
}
- 示例三:释放锁
void ReleaseMutexLock(void *ptr)
{
void **pptr = (void **) ptr;
if (*pptr != NULL)
{
pthread_mutex_unlock(*pptr);
}
}
void test_whenLock_thenFree(pthread_mutex_t *lock)
{
RAII_FREE(ReleaseMutexLock) pthread_mutex_t *mutex = lock;
pthread_mutex_lock(mutex);
}
理论依据
1、现有的语言c++、rust都有RAII的实现
2、 Linux平台下最常用的C语言函数库 (glib) 中的公共 API (g_autoptr)中使用了该能力
3、A simple defer feature for C(2021)这篇论文也讨论了c的defer关键字,即在函数返回之前执行,原理跟cleanup是一样的
考虑到__attribute__是编译属性,笔者调研了几款常见编译器,支持程度如下:
编译器 | 是否支持 | 参考 |
---|---|---|
GCC | 支持 | https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html |
Clang | 支持 | https://clang.llvm.org/docs/AttributeReference.html |
MSVC | 不支持,可以通过 _try 和 _finally 关键字实现类似的功能 | https://learn.microsoft.com/en-us/cpp/c-language/try-finally-statement-c?view=msvc-170 |
优缺点
- 优点:
- 宏简单易用,并且没有在宏里定义复杂逻辑,debug和运维都很方便
- 使代码结构简单画(减少变量/资源清理的代码或者goto跳转)
- 降低内存泄露、打开文件、锁定互斥锁的风险
- 缺点:
- 不完全惯用的c风格
- 需要依赖编译器来清理
扩展
- 智能指针:FreeFunc中加入引用技术,则可实现c++中的智能指针功能