Skip to content

Commit

Permalink
Detail how to remove a comma when no extra arguments are passed
Browse files Browse the repository at this point in the history
  • Loading branch information
agagniere committed Aug 18, 2024
1 parent f8bf600 commit 058e62b
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 1 deletion.
41 changes: 40 additions & 1 deletion book/pages/03_log.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ Fortunatly, the C language also features a way for macros to be variadic, which
:::::{dropdown} How to define and use variadic macros ?
:icon: question
:color: primary
:open:

A variadic macro is a function-like macro that takes a variable number of arguments.

Expand Down Expand Up @@ -183,6 +182,46 @@ The magic macro `__VA_OPT__` can be used to remove certain characters when `__VA
::::
:::::

Let's not rush it, as it is tempting to simply add `...` and `__VA_ARGS__` like so:
```prepro
#define log_log(LEVEL, MESSAGE, ...) \
printf("|" LEVEL "|`" __FILE__ "`|`%s`|%i|" MESSAGE "\n", __func__, __LINE__, __VA_ARGS__)
#define log_error(MESSAGE, ...) log_log("ERROR", MESSAGE, __VA_ARGS__)
log_error("Failed to open %s: %s", filename, error);
```

This code causes a compilation error when passing no additional parameters: `log_error("Out of memory");`{l=C} expands to `printf("[...]", __func__, __LINE__, );`{l=C} with an extraneous comma !

To solve this issue we will use the magic macro `__VA_OPT__`{l=prepro} to remove the comma when `__VA_ARGS__`{l=prepro} is empty:

:::{preprocessed} 03_variadic_macro
:output: none
:::

::::{dropdown} History lesson
:icon: info
:color: primary

`__VA_OPT__`{l=C} was introduced in C23. Before that, there was no portable way to remove characters when `__VA_ARGS__`{l=C} is empty.

But because removing a comma when there are no arguments is such a common need, GNU introduced a syntax to specifically remove a comma before `__VA_ARGS__`{l=C} (and nothing else !)

:::{preprocessed} 03_vm_gnu
:no-compiler-view:
:output: none
:::

_Source_: {bdg-link-secondary-line}`GNU <https://gcc.gnu.org/onlinedocs/gcc-12.2.0/gcc/Variadic-Macros.html>`

A portable workaround was to always have at least one mandatory argument, and not giving it a name:
:::{preprocessed} 03_vm_portable
:no-compiler-view:
:::

But as you can tell, it adds a lot of limitations.
::::

## Convert the line number to a string literal

Expand Down
10 changes: 10 additions & 0 deletions book/samples/03_vm_gnu.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#define logger(MESSAGE, ...) printf("[log] " MESSAGE "\n", ##__VA_ARGS__)
#define TWICE(...) __VA_ARGS__, ##__VA_ARGS__

int main()
{
logger("Hello");
logger("Hello %s", "World");
int zero[] = {TWICE()};
int four[] = {TWICE(2, 4)};
}
8 changes: 8 additions & 0 deletions book/samples/03_vm_portable.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#define logger(...) printf("[log] " __VA_ARGS__)

int main(int ac, char** av)
{
logger("Hello\n");
logger("argument count: %i\n", ac);
logger("program name : %s\n", *av);
}

0 comments on commit 058e62b

Please sign in to comment.