Is the standard C assert(e)
macro permitted to evaluate e
multiple times? What about C++11 or later? I don't see any guarantees in the Open Group spec, and the answer isn't apparent to me from some searching (1, 2).
Context: could func()
be called multiple times in assert(func() != NULL)
?
Yes, I already know this is a bad idea for other reasons: as the glibc manual points out, the argument of assert()
won't be evaluated at all if NDEBUG
is defined. However, assuming NDEBUG
is not defined, is there any guarantee on the maximum number of times e
is evaluated?
Question prompted by this one.
In the C Programming Language, assert is a macro that is designed to be used like a function. It checks the value of an expression that we expect to be true under normal circumstances. If expression is a nonzero value, the assert macro does nothing.
The assert() macro is used to check expressions that ought to be true as long as the program is running correctly. It is a convenient way to insert sanity checks.
C library macro - assert() The C library macro void assert(int expression) allows diagnostic information to be written to the standard error file. In other words, it can be used to add diagnostics in your C program.
The macro NDEBUG denotes not using debugging information. If NDEBUG is defined, then assert is defined as an expression which does nothing. Otherwise assert will print debugging information if the expression it tests is false.
In the C11 standard (ISO/IEC 9899:2011), §7.1.4 Use of library functions says:
Each of the following statements applies unless explicitly stated otherwise in the detailed descriptions that follow: …
Any invocation of a library function that is implemented as a macro shall expand to code that evaluates each of its arguments exactly once, fully protected by parentheses where necessary, so it is generally safe to use arbitrary expressions as arguments.186) Likewise, those function-like macros described in the following subclauses may be invoked in an expression anywhere a function with a compatible return type could be called.187)
186) Such macros might not contain the sequence points that the corresponding function calls do.
187) Because external identifiers and some macro names beginning with an underscore are reserved, implementations may provide special semantics for such names. For example, the identifier
_BUILTIN_abs
could be used to indicate generation of in-line code for theabs
function. Thus, the appropriate header could specify#define abs(x) _BUILTIN_abs(x)
for a compiler whose code generator will accept it. In this manner, a user desiring to guarantee that a given library function such as
abs
will be a genuine function may write#undef abs
whether the implementation’s header provides a macro implementation of
abs
or a built-in implementation. The prototype for the function, which precedes and is hidden by any macro definition, is thereby revealed also.
The preamble in §7.2 Diagnostics <assert.h>
says:
The
assert
macro shall be implemented as a macro, not as an actual function. If the macro definition is suppressed in order to access an actual function, the behavior is undefined.
And section §7.2.1.1 The assert
macro says:
The
assert
macro puts diagnostic tests into programs; it expands to a void expression. When it is executed, ifexpression
(which shall have a scalar type) is false (that is, compares equal to 0), theassert
macro writes information about the particular call that failed (including the text of the argument, the name of the source file, the source line number, and the name of the enclosing function — the latter are respectively the values of the preprocessing macros__FILE__
and__LINE__
and of the identifier__func__
) on the standard error stream in an implementation-defined format.191) It then calls theabort
function.191) The message written might be of the form:
Assertion failed:
expression
, function
abc
, file
xyz
, line
nnn
.
So much for the verbiage of the standard — how does that translate in practice?
A lot hinges on the interpretation of the statement:
If assert
is regarded as a function that is implemented via a macro, then its argument shall be evaluated just once (the conversion to string is a compile-time operation that does not evaluate the expression).
If assert
is regarded as 'not a function' (because it is explicitly a macro), then the restriction quoted doesn't necessarily apply to it.
In practice, I'm sure that the intent is that the expression argument to assert
should only be evaluated once (and that only if NDEBUG
was not defined when the <assert.h>
header was last included) — so I'd regard it as being constrained as if it was a function that is implemented via a macro. I'd also regard any implementation that implemented assert
in such a way that the expression was evaluated twice as defective. I'm not certain that the quoted material supports that, but it is all the relevant material I know of in the standard.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With