On 09/01/2026 13:07, Alejandro Colomar via Gcc wrote:
Hi!
I'm trying to find a way to verify that an expression (usually passed as
an argument to a macro) has no side effects. Is this possible?
Let's say I want to implement strnul():
#define strnulA(s) (s + strlen(s)) // Problem: evaluates twice
#define strnulB(s) \
({ \
auto s_ = s; \
s_ + strlen(s_); \
})
The problem with strnulB() is that it has a local variable 's_'. This
means I can't pass an argument called s_. That is, the following call
doesn't work:
strnulB(s_);
That is always the risk of macros. Usually people just pick parameter
names and local variable names that are very unlikely to collide. If
you have -Winit-self (or -Werror=init-self) then collisions will be
detected (as "auto s_ = s_;_" now gives a warning or error).
You can also use __LINE__ to generate unique symbols :
#define strnulX(s) ({ \
auto s__ ## __LINE__ = (s); \
s__ ## __LINE__ + strlen(s__ ## __LINE__ ); \
})
But here you can probably just use a static inline function. It is,
IME, always better to use a static inline function than a function-like
macro when possible:
static inline char * strnulY(char * s) {
return s + strlen(s);
}
<https://godbolt.org/z/oGvcfbh14>
Is there a way to implement something like this?:
#define strnulC(s) \
({ \
static_assert(!has_side_effects(s)); \
s + strlen(s); \
})
If there's no way, it would be interesting to add compound literals of
function type to achieve this:
#define strnulD(...) \
((static inline auto \
(auto s)) \
{ \
return s + strlen(s); \
}(__VA_ARGS__))
Which guarantees that the argument is evaluated once.
Do you need your macro here to work with different types? I'm not sure
how that could make sense, since "strlen" is not generic and always
treats its parameter as a char*.
If you do need something that can work with different types and you want
to use functions to avoid possible issues with macro parameters, you can
use _Generic :
#define strnulX(s) _Generic((s), \
char * : strnulX_char(s), \
char16_t * strnulX_char16(s), \
char32_t * strnulX_char32(s)
)(s)
Then define functions strnulX_char, strnulX_char16, and strnulX_char32
as separate functions. (_Generic does not evaluate its argument, so
side-effects don't matter to it.)