// Simulating RAII in C
#import "/template-en.typ":*
#doc-template(
title: "Simulating RAII in C",
date: "March 5, 2022",
body: [
#set heading(numbering: none)
Resource safety is much more difficult in C than in C++. With the RAII mechanism in C++, resource safety is quite handy. No wonder the first half of Stroustrup's "The C++ Programming Language" is about memory safety and resource safety, all of which are guaranteed by RAII. Once a local variable goes out of scope, its destructor is called, which is very convenient.
However, C language does not have such a useful tool, for example:
```c
int foo() {
FILE* fp = fopen("bar", "w");
if (f == 0) {
error("failed to open file");
return -1;
}
int ret = do_something(fp);
if (ret < 0) {
error("failed to process file");
fclose(fp);
return -1;
}
fprintf(fp, "this end it");
fclose(fp);
return 0;
}
```
This is just a simple example, and `fclose` appears twice here; however, when the program's control flow becomes complex, resource recovery becomes terrifying. Unlike in C++, where you only need to open an `ofstream` and then leave the rest to the destructor.
In addition to C++, other languages have similar mechanisms. For example, Java and Go have garbage collection for handling memory. As for other resources, such as file handles, network connections, mutexes, etc., Java uses `try...catch...finally...`, and Go uses `defer`.
So what should be done in C? Fortunately, GCC provides a #link("https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes")[cleanup extension] that can be used to register destructors.
The above example of closing a file can be rewritten using this extension as follows:
```c
void close_file(FILE** fp_ptr) {
if (*fp_ptr == NULL) return;
fprintf(*fp_ptr, "file is closed\n");
fclose(*fp_ptr);
}
int foo() {
__attribute__((cleanup(close_file))) FILE* fp = fopen("bar", "w");
if (fp == NULL) {
error("failed to open file");
return -1;
}
int ret = do_something(fp);
if (ret < 0) {
error("failed to process file");
return -1;
}
fprintf(fp, "this end it\n");
return 0;
}
```
With this cleanup attribute, `close_file` can be executed automatically, saving the trouble of manual management.
To make the code more compact, a lexical macro can also be added.
```c
#define CLEANUP(func) __attribute__((cleanup(func)))
```
Mutexes are similar:
```c
pthread_mutex_t mutex;
int count;
void unlock_mutex(pthread_mutex_t **mutex_ptr) {
pthread_mutex_unlock(*mutex_ptr);
}
void *thread_run(void *arg){
int i;
int ret = pthread_mutex_lock(&mutex);
if (ret != 0) {
error("failed to acqure lock");
return 0;
}
CLEANUP(unlock_mutex) pthread_mutex_t *defer_mutex = &mutex;
for (i = 0; i < 3; i++) {
printf("[%ld]count: %d\n", pthread_self(), ++count);
}
return 0;
}
int main() {
pthread_t threads[10];
for (int i = 0; i < 10; i++) {
int res = pthread_create(&threads[i], NULL, thread_run, NULL);
if (res) error("create thread error");
}
for (int i = 0; i < 10; i++) {
void *ret;
pthread_join(threads[i], &ret);
}
return 0;
}
```
Although this is a GCC extension, the Clang/LLVM toolchain also supports it.
If you want a more general way, you can also use the `goto` statement. Although `goto` is generally considered a bad practice, in the scenario of resource recovery, it is actually considered a good practice:
```c
int foo() {
FILE* fp = fopen("bar", "w");
if (f == 0) {
error("failed to open file");
goto clean_0;
}
int ret = do_something(fp);
if (ret < 0) {
error("failed to process file");
goto clean_1;
}
fprintf(fp, "this end it");
fclose(fp);
return 0;
clean_1:
fclose(fp);
clean_0:
return -1;
}
```
Alternatively, you can also try using macros:
```c
int foo() {
FILE* fp = NULL;
#define DEFER \
if (fp != NULL) fclose(fp);
fp = fopen("bar", "w");
if (f == 0) {
error("failed to open file");
DEFER return -1;
}
int ret = do_something(fp);
if (ret < 0) {
error("failed to process file");
DEFER return -1;
}
fprintf(fp, "this end it");
DEFER return 0;
#undef DEFER
}
```
= Summary
All in all, the `goto` statement seems to be the best.
On the other hand, there is also a #link("http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2895.htm")[proposal to add defer to the C language], but no one knows whether it will enter the standard, so let's wait and see.
])
Email: i (at) mistivia (dot) com