This is not a sound advice when resource allocations depend on each other. There is a good reason why deref
guarantee a reverse execution order.
I think it has to do with the goal of maximum code readability, and specifically, making it easier to understand what code is doing because there’s only one way to do things, so what you see is what you get. If Zig had defer
, errdefer
, and goto
, you could have some people using goto
for cleanup and others using the defer
s, and maybe others using sometimes one or the other inconsistently. That would hurt readability.
How they (resources) can?
I only know 3 (three) kinds of resources:
- file descriptors, in a broad sense
- pointers to memory locations
- mutexes, if you adhered to do concurrency via threads
Show an example of how and why those 3 might depend on each other.
I’d imagine @slonik-az meant dependencies such as in the below code. Consider the function FileReader_new
. If there’s an error, you have to close the file resource first, then free the memory allocated. If you reverse the order of operations (free first, close second), you’d get a use-after-free on self->fp
. IOW, order matters. The example is maybe a little contrived, but IMO this reversal of resource free operations is common pattern, and something I didn’t think could be controversial.
I’m also in the camp who think defer
and errdefer
do a pretty good job as language features for making it easier to free resources correctly.
#include <stdio.h>
#include <stdlib.h>
typedef struct {
FILE* fp;
} FileReader;
static int peekHeader(FILE* fp) {
// do something with file handle that can fail,
// and return false in that case.
// otherwise return true for success
return 1;
}
FileReader* FileReader_new(const char* fname) {
FileReader* self = malloc(sizeof(FileReader));
self->fp = fopen(fname, "rb");
if (!peekHeader(self->fp)) {
fclose(self->fp); // fclose file first
free(self); // free memory second
return NULL;
}
return self;
}
FileReader_destroy(FileReader* self) {
fclose(self->fp); // close file first
free(self);
}
Nope, it does not.
bool do_smth(... result *r) {
int fd = -1;
void *mm = NULL;
fd = open(...);
if (-1 == fd1) {
...
goto __failure;
}
mm = calloc(...);
if (NULL == mm) {
...
goto __failure;
}
r->fd = fd;
r->mm = mm;
return true;
__failure:
if (-1 != fd)
close(fd);
if (NULL != mm)
free(mm);
return false;
}
You can swap close
and free
clauses, nothing would change.
There are many more resource types than the 3 you listed. For example databases. (1) allocate string for DB’s url (2) open db at this url and return its handle that has a pointer to the allocated URL string. Now deallocate your resources in the same order (1) free the url string (2) disconnect from the db … oops, accessing the url string that was already freed. segfault.
Bottom line, deallocation order is always inverse of allocation order. In some special cases with no dependencies you can get away with arbitrary order. But it is not a sound advice and is cause of subtle bugs. If you are interested more in this subject there are many books on C++ that explain why destructors are always called in reverse order of constructors.
Yep, I saw this pattern a lot. Never fully understood why do{...}while(0);
rather than simply {...}
. Is it to force ;
at the end of the statement?
do-while
is for being able to break
(goto) out of the block. I think you need to be in some sort of looping context in order for break to be legal in C.
LOL yes, it was just a fragment of an example, to show that you don’t strictly need goto
in C for well-structured cleanups.
In C/C++ one needs a for/while
loop to use break
. But I saw this do-while(0)
trick without any break
in it and am wondering what would be the reason.
Exactly this.
The do ... while(0)
trick, without a break
/ continue
, is used, as you said, to convert a multi-statement piece of code into a single statement. I usually see it in macro definitions, to ensure that, for example, using the macro inside an if
/ else
without braces still guarantees proper nesting.