Finish curious case of gebs
This commit is contained in:
@ -12,8 +12,9 @@ GEBS also includes a bunch of extra helper macros, which turn C into a language
|
|||||||
So one thing I've noticed is that nob.h is used alongside of [arena](https://github.com/tsoding/arena). If you look into the implementation
|
So one thing I've noticed is that nob.h is used alongside of [arena](https://github.com/tsoding/arena). If you look into the implementation
|
||||||
you can see some things, which are somewhat redundant like \`arena_sprintf()\` or \`arena_da_append()\`, \`arena_sb_append_cstr()\` and so on...
|
you can see some things, which are somewhat redundant like \`arena_sprintf()\` or \`arena_da_append()\`, \`arena_sb_append_cstr()\` and so on...
|
||||||
First of all, why is an arena library managing string builders and dynamic arrays? In my opinion it should be the other way around.
|
First of all, why is an arena library managing string builders and dynamic arrays? In my opinion it should be the other way around.
|
||||||
A string builder should rather accept a generic allocator interface, which it can then utilize to get it's memory. In GEBS this is done
|
A string builder should rather accept a generic allocator interface, which it can then utilize to get it's memory.
|
||||||
via a \`Gebs_Allocator\` interface.
|
Basically we supplement a dynamic structure with an allocator of choice.
|
||||||
|
In GEBS this is done via a \`Gebs_Allocator\` interface.
|
||||||
|
|
||||||
\`\`\`c
|
\`\`\`c
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -58,3 +59,82 @@ This is my version of the \`XXX_da_append()\` macro:
|
|||||||
This way a dynamic list can work with any kind of allocator - the default libc allocator, an arena or literally anything else.
|
This way a dynamic list can work with any kind of allocator - the default libc allocator, an arena or literally anything else.
|
||||||
We're not tied to the libc allocator and then have to implement the same macro of all other allocators.
|
We're not tied to the libc allocator and then have to implement the same macro of all other allocators.
|
||||||
|
|
||||||
|
### Defer macro
|
||||||
|
|
||||||
|
Ever forgot to place a \`free()\` call on function exit or an \`fclose()\`? The defer macro comes to the rescue. Here's a short snippet:
|
||||||
|
(Taken straight form the source code of this website btw.)
|
||||||
|
|
||||||
|
\`\`\`c
|
||||||
|
cJSON *root = cJSON_CreateObject();
|
||||||
|
defer { cJSON_Delete(root); }
|
||||||
|
|
||||||
|
char *time = __TIME__;
|
||||||
|
uchar md5_buf[16];
|
||||||
|
md5String(time, md5_buf);
|
||||||
|
String_Builder sb = {0};
|
||||||
|
defer { sb_free(&sb); }
|
||||||
|
for (size_t i = 0; i < 16; i++) {
|
||||||
|
sb_append_nstr(&sb, fmt("%02x", md5_buf[i]));
|
||||||
|
}
|
||||||
|
sb_finish(&sb);
|
||||||
|
|
||||||
|
cJSON_AddItemToObject(root, "build_id", cJSON_CreateString(sb.items));
|
||||||
|
|
||||||
|
make_application_json(result, 200, root);
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
If not for the \`defer { ... }\` macro, remebering when to free memory would have been quite hellish.
|
||||||
|
Another example:
|
||||||
|
|
||||||
|
\`\`\`c
|
||||||
|
NString_List env = {0};
|
||||||
|
defer { list_free(&env); }
|
||||||
|
|
||||||
|
String_Builder out = {0};
|
||||||
|
defer { sb_free(&out); }
|
||||||
|
|
||||||
|
char path[PATH_MAX] = {0};
|
||||||
|
if (!get_baked_resource_path("home.html", path, sizeof(path))) {
|
||||||
|
make_internal_server_error(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
On \`return\` we'd have to **NOT FORGET** to add \`list_free()\` and \`sb_free()\`, but now that we have our defer,
|
||||||
|
we can kind of shut the brain off and not concern ourselves with freeing the memory. We can be 100% sure it's going to
|
||||||
|
be freed if we step into the return statement.
|
||||||
|
|
||||||
|
The implementation is quite simple, actually
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
#define defer defer__2(__COUNTER__)
|
||||||
|
#define defer__2(X) defer__3(X)
|
||||||
|
#define defer__3(X) defer__4(defer__id##X)
|
||||||
|
#define defer__4(ID) auto void ID##func(char (*)[]); __attribute__((cleanup(ID##func))) char ID##var[0]; void ID##func(char (*ID##param)[])
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Source article: https://gustedt.wordpress.com/2025/01/06/simple-defer-ready-to-use/
|
||||||
|
|
||||||
|
### compile_flags.txt
|
||||||
|
|
||||||
|
Clang/LLVM docs: https://clang.llvm.org/docs/JSONCompilationDatabase.html
|
||||||
|
|
||||||
|
I use clangd inside of my vim. Clangd can be configured via a json database compile_commands.json. It's quite complicated for GEBS in a sense
|
||||||
|
that it uses the \`XX.c -> XX.o\` building pattern, while GEBS is focused more on unity builds (it's on the programmer to implement caching).
|
||||||
|
Luckily, clangd can be configured via a simple and minimalistic config file - \`compile_flags.txt\`, which holds only compiler flags that
|
||||||
|
are used to compile our C files. We can for eg. put some include paths in there and clangd will pick them up.
|
||||||
|
|
||||||
|
In GEBS we can generate a \`compile_flags.txt\` file using a built-in macro:
|
||||||
|
|
||||||
|
\`\`\`c
|
||||||
|
#define CFLAGS \\
|
||||||
|
"-I.", \\
|
||||||
|
"-I./some-lib", \\
|
||||||
|
"-Wall", \\
|
||||||
|
"-Wextra" \\
|
||||||
|
|
||||||
|
// #define other stuff like CC, LDFLAGS, SOURCES
|
||||||
|
|
||||||
|
make_compile_flags(CFLAGS); // Will output the file
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user