Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

May a standards-compliant C assert() evaluate its argument multiple times?

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.

like image 422
cxw Avatar asked Sep 23 '16 15:09

cxw


People also ask

What does assert () do in C?

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.

What is the purpose of the assert () macro?

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.

What library is assert in C?

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.

What is Ndebug?

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.


1 Answers

The C standard says

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 the abs 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, if expression (which shall have a scalar type) is false (that is, compares equal to 0), the assert 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 the abort function.

191) The message written might be of the form:
Assertion failed:expression, functionabc, filexyz, linennn.

A possible interpretation of the standard

So much for the verbiage of the standard — how does that translate in practice?

A lot hinges on the interpretation of the statement:

  • Any invocation of a library function that is implemented as a macro shall expand to code that evaluates each of its arguments exactly once

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.

like image 163
Jonathan Leffler Avatar answered Sep 30 '22 15:09

Jonathan Leffler