Testing if an expression has side effects at compile time
David Brown
david@westcontrol.com
Fri Jan 9 16:39:55 GMT 2026
On 09/01/2026 16:44, Alejandro Colomar via Gcc wrote:
> Hi David,
>
> On Fri, Jan 09, 2026 at 04:10:56PM +0100, David Brown wrote:
>> On 09/01/2026 13:07, Alejandro Colomar via Gcc wrote:
>>> #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).
>
> Yup, I have that enabled, and indeed is triggering.
>
>> You can also use __LINE__ to generate unique symbols :
>>
>> #define strnulX(s) ({ \
>> auto s__ ## __LINE__ = (s); \
>> s__ ## __LINE__ + strlen(s__ ## __LINE__ ); \
>> })
>
> This doesn't solve it, because it is called from another macro, which
> has the same __LINE__ value.
>
You are really setting yourself up for a challenge here, aren't you? :-)
>> 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);
>> }
>
> I can't use this. I want something that preserves 'const', like the
> C23 APIs using QChar.
>
> I could in fact do both a function and a macro, plus all the magic for
> doing QChar, but that's too much complexity, and would be better solved
> with 'auto' in inline functions.
>
static inline char * strnulZ_char(char * s) {
return s + strlen(s);
}
static inline const char * strnulZ_cchar(const char * s) {
return s + strlen(s);
}
#define strnulZ(s) _Generic((s), \
char * : strnulZ_char, \
const char * : strnulZ_cchar \
)(s)
Sure, it would be easier if C supported templates, but it is a /lot/
easier to write one _Generic macro than for the GCC developers to
implement templates in C!
You might consider switching to C++ as an alternative, even if you only
use a few of the language's features (like templates).
David
>
> Have a lovely day!
> Alex
>
>>
>>
>> <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.)
>>
>>
>
More information about the Gcc
mailing list