C23 introduced the attributes [[reproducible]]
and [[unsequenced]]
.
- What is the motivation behind them?
- How are they defined, and what effect do they have on a function?
- What kind of functions should I apply them to?
C23 introduced the attributes [[reproducible]]
and [[unsequenced]]
.
Copyright © 2021 Jogjafile Inc.
The motivating problem is that the compiler has no insight into functions when no definition of the function is available. This prevents almost all compiler optimizations (unless LTOs are used).
Consider the following example:
Even though the compiler has no definition of
square
, it is allowed to perform two optimizations:[[reproducible]]
,square(2)
yields the same result when called twice in a row, and the compiler can decide to callsquare(2)
only once[[unsequenced]]
, calls tosquare
can be made in any order, and the compiler could even decide to evaluatesquare(2)
just once at program startup. It can also decide to evaluatesquare(3)
beforesquare(2)
, if this is somehow more efficient.Such optimizations can also be made by defining functions
inline
in headers, and letting the compiler infer these properties on its own. However, for complicated functions, making everythinginline
isn't feasible due to the added compilation slowdown.Semi-Formal Definitions
For a more rigorous explanation, see the C23 standard working draft N3096 §6.7.12.7 Standard attributes for function types.
[[reproducible]]
This attribute asserts that a function is a reproducible function, which means that
Effectless restricts what state a function can modify. If any non-local state is modified, this can only happen through pointers passed to it. For example, a
void to_upper_case(char *str)
function is effectless if it only modifies local variables and the contents ofstr
. (Intuitively, the function has no observable side effects.)Idempotent means that calling the function multiple times has the same effect as calling it once. For example, we can call
to_upper_case(s); to_upper_case(s);
, and it would have the same effect as calling it just once.[[unsequenced]]
This attribute asserts that a function is an unsequenced function, which means that
Stateless means that
static
orthread_local
local variables cannot be non-const
, and cannot bevolatile
.Independent means that all calls of the function will see the same values for global variables, won't change global state, and won't change any state through pointer parameters.
to_upper_case
is not independent, but a function likestrlen
can be.Intuitively, an unsequenced function can be arbitrarily sequenced, and even sequenced in parallel between changes to its observed state: (see also footnote 196 in the standard)
In this example, there can be one, two, or infinitely many calls to
strlen
between pointsA
andB
. These can happen sequentially, or in parallel. No matter what, the outcome must be the same for an unsequenced function. The mutation ofglobal
is not allowed to change the result ofstrlen
.Note on GCC attributes
The GCC attributes
pure
andconst
are the inspiration for these standard attributes, and behave similarly. See N2956 5.8 Some differences with GCC const and pure for a comparison. In short:pure
is more relaxed than[[reproducible]]
const
is more strict than[[unsequenced]]
When To Use These Attributes
These attributes are meant for advanced users who want to take advantage of compiler optimizations.
In general, you have to be quite careful with applying them. The program is ill-formed, no diagnostic required if you apply them to a function which doesn't have the asserted properties. Compilers are encouraged to detect such misuse of these attributes, but this isn't required.
Common Examples (and surprises)
printf
is obviously neitherstrlen
andmemcmp
can be[[unsequenced]]
(Can strlen be [[unsequenced]]?)memcpy
can be[[reproducible]]
memmove
can't be either, because it isn't idempotent for overlapping memory regionsfabs
can be[[unsequenced]]
sqrt
can't be either, because it modifies the floating point environment and may seterrno
See Also