Initialization, allocation and effective type

Jens Gustedt, INRIA and ICube, France
Martin Uecker, Graz University of Technology, Austria

2023-12-13

document history

document number date comment
n3186 202312 this paper, original proposal

license

CC BY, see https://creativecommons.org/licenses/by/4.0

1 Overview over the proposed allocation and initialization features

The proposed type-generic macros are intended as simple to use interfaces that, contrary to other existing library functions, provide strong guarantees for initialization of array objects and allow to guarantee (and even overwrite) the validity and effective type of the corresponding object and each of its elements. In particular,

Because of these guarantees, to our knowledge currently they cannot be implemented without implementation-specific knowledge. In particular,

For the latter, an implementation must for example be able to guarantee, that no information about a future use of the storage can be deduced (which could e.g be made possible by cross-TU analysis such as link-time optimization) and that thus any initialization that is requested by the specification can not be skipped under any circumstances.

The first set of functions (stdc_init and stdc_alloc) is destined for ordinary array objects, the second set (stdc_init_flex, stdc_alloc_flex and stdc_realloc_flex) is destined for structures with flexible array members.

2 Initialization and allocation of ordinary arrays

2.1 The stdc_init feature

7.26.2.7 The stdc_init type-generic macro

Synopsis

#include <string.h>

stdc_init(assignment-expression, assignment-expression, type-name, initializer-list)
stdc_init(assignment-expression, assignment-expression, type-name)
stdc_init(assignment-expression, assignment-expression)

Description

2 The first argument to a invocation to the stdc_init macro shall be an assignment expression L that has integer type and a value n such that 0 < n ≤ SIZE_MAX. The second argument shall be an assignment expression XP that has an array type or a pointer type. If XP has an array type, P denotes the resulting pointer after array-to-pointer conversion; otherwise P is the same as XP. The target type of P shall not be const qualified and P shall not be a null pointer constant. If a invocation has a third argument, it shall be a type name T that shall either be a void type or a non-atomic complete object type; if no third argument is given T defaults to void. If four or more arguments are given, the additional arguments shall form an initializer list INIT and T shall not be an array type.

3 A call (i.e an invocation that is met during execution) to the stdc_init type-generic macro creates a new instance of an array object with an address of value P; the lifetime of any object that resided in the same storage ends with the call. The new object is initialized as an array of L elements of type T, if T is a complete object type, or as array of bytes as-if by a call to memset_explicit if it is a void type. All argument expressions including initializers are evaluated at most once. If initializers are specified, then (by the above) T is not an array type and each array element (or byte) is initialized as if assigned with a compound literal (typeof(T)){ INIT } or as if by a call memset_explicit(P, (INIT), L), respectively. If there is no initializer and T is a complete type the array is initialized as if by default initialization; if it is a void type it is initialized as-if by a call memset_explicit(P, 0, L). The result value and type of the call is (typeof(T)*)P and refers to the first element of an array object with at least L members.

4 If T is a complete type other than a non-atomic character type the effective type of the elements of the new instance of the array object after the call is T. If T is a void type or a non-atomic character type:

5 The type of P shall be typeof(T)*, implicitly convert to that type, or be a pointer to a non-atomic character type. The storage to which P refers shall be mutableFNTx) and be large enough to hold the array object. If T is a type other than a void type or than a non-atomic character type, R is the return value of a call and V is an lvalue that is formed that is based on R or any of its copies: if the object created by the call is accessed through an lvalue W which is not based on R and that has a type that is not a non-atomic character type and, the behavior is undefined.FNTy)

FNTx) Even though narrow string literals have type compatible to char[] or unsigned char[], they are immutable and thus not suited as storage for a call to stdc_init.

FNTy) Thus, the returned pointer value has all the properties of the evaluation of a pointer object that is restrict qualified.

Returns

6 If T is a complete type, a call to the stdc_init type-generic macro results in a pointer to the first element of a newly created and properly initialized array object of effective type typeof(T)[L] which has the same address as P. If T is a void type, the values of all bytes and the effective type are as indicated.

Example 1

7 A call to stdc_init can be used to initialize a buffer with static storage duration at each entry into a function.

double f(double x[2]) {
    static double dat[10];
    // The return value is never used and the base types agree.
    stdc_init(10, dat, double, x[0]+x[1]);
    ...
    // do something with dat
    return dat[0];
}

double g(double x[2]) {
    static double buf[10];
    double*restrict p = stdc_init(10, buf, double, x[0]+x[1]);
    // No access to identifier buf below.
    ...
    // do something with p
    return p[0];
}

double h(double x[2]) {
    auto restrict q = stdc_init(10, (static double buf[10]){ }, double, x[0]+x[1]);
    ...
    // do something with q
    return q[0];
}

Note that for f the return value of the call is not used, and thus no lvalue is ever formed. In contrast to that, function g uses the return value and the return expression forms an lvalue that is based on p. Thus an access to the object other than using p (or a copy) has undefined behavior. An equivalent definition h uses a compound literal of static storage duration. This enforces that the buffer is not accessible by other means than the pointer q.

Example 2

8 Calls to stdc_init can be used to initialize elements of a list individually, as in the first function, or to initialize a whole array uniformly with the same values, as in the second.

typedef struct ele ele;
struct { ele* next; size_t pos; float data; };

ele* generate_list(size_t len) {
    ele* head = nullptr;
    for (size_t i = 0; i < len; i++) {
        head = stdc_init(1, malloc(sizeof(ele)), ele, .pos = i, .next = head);
    }
    return head;
}

ele (*generate_tree(size_t len)[]) {
    auto restrict head = stdc_init(1, malloc(sizeof(ele)), ele, .pos = 0);
    auto restrict leaves = (ele (*)[len])stdc_init(len, malloc(sizeof(ele)), ele, .pos = 1, .next = head);
    return leaves;
}

int main(int argc, char argv[]) {
    ele (*vlap)[argc]   = generate_tree(argc); // valid
    ele (*plap)[argc+1] = generate_tree(argc); // undefined behavior
}

The first function returns a pointer to the sole element of a 1 element array. The return of the second function is a pointer to an incomplete array. Within the function that type is compatible with the type of leaves and thus the return expression is valid. For the purpose of forming a prototype the length information of the return type is lost. Callers of the function have to ensure that the return value is used with a compatible pointer to array type.

Example 3

9 Calls to stdc_init can be used to initialize a whole object to a specific effective type and then again to zero out all bits such that all implicit effective type information is removed.

unsigned char buffer[100];
double*restrict dp = stdc_init(sizeof buffer/sizeof(double), buffer, double, 3.5);
// Only accessible through dp and as double[N]
...
void*restrict ep = stdc_init(sizeof buffer, dp);
// Only accessible through ep, has no effective type
// No information of use as double[N] can be retrieved.
...
assert(dp == ep);       // pointers compare equal
dp <= ep;               // undefined behavior

The two pointers dp and ep have the same abstract address, so they compare equal. Nevertheless they don’t refer to elements of the same array object, so performing any relational comparison between the two pointer values has undefined behavior.

2.2 The stdc_alloc feature

7.24.3.9 The stdc_alloc type-generic macro

Synopsis

#include <stdlib.h>

stdc_alloc(assignment-expression, type-name, initializer-list)
stdc_alloc(assignment-expression, type-name)
stdc_alloc(assignment-expression)

Description

2 Each invocation of the macro behaves as if a suitable function with a [[nodiscard]] attribute is called. The arguments shall have the same properties as the first (L), the third (T) and following arguments (INIT) to the stdc_init type-generic macro. The stdc_alloc macro allocates an object of suitable size as if by a call to malloc and initializes it as an array of L elements as if by a call to stdc_init.

3 The allocation fails because L is outside the required range, the requested object size would have been too large, or because not enough resources are available. If the allocation fails

FNT1) That is, if L is not an integer constant expression, the call itself never fails, and results in a null pointer.

Returns

4 If the allocation is successful, the stdc_alloc type-generic macro returns a pointer of type typeof(T)* refering to the first element of an allocated and properly initialized array object of effective type typeof(T)[L] (if T is not a void type) or unsigned char[L]. If the allocation fails and L is not an integer constant expression the result type is a null pointer of the same type.

NOTE 1

5 The specification of an initializer is even valid if the type is void. If the allocation is successful, a call stdc_alloc(L, void, UCHAR_MAX) results in a pointer to void refering to an object with L bytes that is initialized to all bits set to 1.

NOTE 2

6 If for the implementation an initialization to all bits zero is a valid default initialization for the type T (or unsigned char in the case of a void type) and if the allocation is successful, calls stdc_alloc(L), stdc_alloc(L, T), or stdc_alloc(L, T, /*empty*/) are equivalent to the expressions

calloc(L, 1)
(typeof(T)*)calloc(L, sizeof(T))

respectively, only that the type name T is evaluated exactly once.

NOTE 3

7 If L is an integer constant expression and the allocation fails, the behavior is undefined. This property is intended to provide the translator with the information that the size of the object is fixed and that diagnostics for violations of the associated constraints are expected. For applications that may be resource critical it is recommended to replace L by an expression with the same value but that is not an integer constant expression, and to provide a failure path by testing for the return value.

3 Initialization, allocation and reallocation of structures with flexible array members

Flexible array members require special precautions because their initial segment may or may not reside within the defining structure, and because the defining structure may have a member that tracks the size of the flexible array member. Therefor the following type-generic macros additionally also receive the name of the flexible array member and of an integer member that holds the array length as additional arguments.

3.1 The stdc_init_flex feature

7.26.2.7 The stdc_init_flex type-generic macro

Synopsis

#include <string.h>

stdc_init_flex(assignment-expression, assignment-expression, type-name, member-designator, member-designator, initializer-list)
stdc_init_flex(assignment-expression, assignment-expression, type-name, member-designator, member-designator)

Description

2 Each invocation of the macro behaves as if a suitable function with a [[nodiscard]] attribute is called. A call to stdc_init_flex shall have five or more arguments; arguments L, XP, T, and initializer list INIT (if any) and pointer value P are determined analogous as for stdc_init, only that T shall name a structure or union type with a flexible array member. The designation of that member FL of type FT[] and the designation LEN of an integer member destined to hold the length L shall be given as fourth and fifth argument to the call similar to the second argument of the offsetof macro. All arguments are evaluated at most once; FL and LEN are not evaluated. The value L shall not be negative and representable in the type of the member LEN.

3 A call to the stdc_init_flex type-generic macro creates a new instance of a structure or union object with flexible array member that is default initialized or initialized by the initializer list (if provided) and with an address of value P. If L is greater than zero, the flexible array member FL is default initialized as an array of type FT[L]. The member LEN shall have an integer type, possibly const-qualified, and is initialized to the value L. The result value and type of the call is then (typeof(T)*)P.

4 The same restrictions about type and usage of the return value as for stdc_init apply, only that additionally the target storage shall have the capacity to store an object of type T with L members in the flexible array member FL, that is it shall have at least a size of the maximum of the two values

sizeof(T)
offsetof(typeof(T), FL) + sizeof(FT[L])

Returns

5 A call to the stdc_init_flex type-generic macro results in a pointer to a newly created object of effective type T with a flexible array member with an array length L which has the same address as P. On return, the storage pointed to by P is initialized and has an effective type of T; if L is greater than zero, the storage pointed to by ((typeof(T)*)P)->FL is initialized as an array with effective type FT[L].

Note

6 A call to the stdc_init_flex macro is equivalent to a sequence of two combined calls to stdc_init as follows. The first call is stdc_init(1, P, T, .LEN = L, INIT) or stdc_init(1, P, T, .LEN = L) respectively, and initializes the structure or union. The second is stdc_init(L, ((typeof(T)*)P)->FL, FT) and initializes the flexible array member by default initialization. If L is small, there may be uninitialized padding after the last element of the flexible array member up to the end of the structure or union. The result value is then the properly converted value of P.

Example

7 A structure with flexible array member can be created inside a buffer of type unsigned char[].

typedef struct flex { size_t const len; double data[.len]; } flex;
constexpr size_t large = 100;
...
static unsigned char buffer[offsetof(flex, data) + sizeof(double[large])];
flex*restrict fp = stdc_init_flex(large, buffer, flex, data, len);   // valid, initializes the const member
printf("%g\n", fp->data[0]);                                         // valid, prints 0
auto restrict gp = stdc_init_flex(large-1, fp, flex, data, len);     // valid
printf("%g\n", gp->data[0]);                                         // valid, prints 0
printf("%g\n", fp->data[0]);                                         // invalid, *fp is dead
auto restrict hp = stdc_init_flex(large+1, buffer, flex, data, len); // invalid, insufficient array length
auto restrict ip = stdc_init_flex(large-1, gp, flex, data, len);     // invalid, gp is indeterminate

Here, the first and second call to stdc_init_flex are valid even though the len member is const qualified; fp->len and gp->len reside within the same storage (but not simultaneously) and are correctly initialized to the values 100 and 99, respectively.

3.2 The stdc_alloc_flex feature

7.26.2.7 The stdc_alloc_flex type-generic macro

Synopsis

#include <string.h>

stdc_alloc_flex(assignment-expression, type-name, member-designator, member-designator, initializer-list)
stdc_alloc_flex(assignment-expression, type-name, member-designator, member-designator)

Description

2 Each invocation of the macro behaves as if a suitable function with a [[nodiscard]] attribute is called. A call to stdc_alloc_flex shall have four or more arguments that fulfill the same requirements as the first (L), third (T), fourth (FL), fifth (LEN) and possibly following arguments of the the stdc_init_flex macro. The effect of a call of this macro is the same as by first calling malloc to allocate a suitably large storage and then, if the allocation is successful, calling stdc_init_flex with the pointer to that storage as second argument.

Returns

3 If the allocation is successful, the stdc_alloc_flex type-generic macro returns a pointer of type typeof(T)* to a newly allocated and properly initialized object of effective type T and if L is greater than zero such that the flexible array member of that object is a properly initialized array of length L. If the allocation fails the result is a null pointer of type typeof(T)*.

3.3 The stdc_realloc_flex feature

7.26.2.7 The stdc_realloc_flex type-generic macro

Synopsis

#include <string.h>

stdc_realloc_flex(assignment-expression, assignment-expression, member-designator, member-designator)

Description

2 Each invocation of the macro behaves as if a suitable function with a [[nodiscard]] attribute is called. The arguments to a call to stdc_realloc_flex fulfill the same requirements as the first (L), second (XP), fourth (FL), and fifth (LEN) arguments of the stdc_init_flex macro. A type T is determined as typeof(*(XP)) and shall be a type with a flexible array member FL and integer member LEN.

The effect of a call to stdc_realloc_flex is similar to a call to realloc: a new object with suitable size is allocated if possible. This new object may reside or not at the same address has *XP. If K is the value of (XP)->LEN and M is the minimum of K and L, data in the union or structure itself and in an initial segment of M elements of the flexible array member is identical to the data in *XP, with the exception of the LEN member which is set to L. If L is greater than K the trailing elements in the new object at postions K to L-1 are initialized as if by stdc_init; if L is less than K before the end of life of *XP the trailing elements in *XP at postions L to K-1 are initialized as if by stdc_init.

Returns

3 If the allocation is successful, the stdc_realloc_flex type-generic macro returns a pointer of type typeof(T)* to a newly allocated and properly initialized object with values as indicated. If the allocation fails the result is a null pointer of the same type.

4 A reference implementation

A reference implementation of these concepts has been undertaken and is presented in the following.

It separates into a header file, that provides the type generic wrappers. A separate translation unit then provides the generic initialization an allocation code; it is important to have this in a separate TU, such that special compiler paramters can be used that hinder the integration of these functions into other TU. For example, that TU should never enable link-time optimization.

Besides such tricks of using different optimization arguments for different TU, this reference implementation only relies on one other feature that is not found in C23: compound expressions. This gnu/clang specific feature could easily be replaced by lambdas, if WG14 choses to go down that road.

4.1 The type-generic header file

Note that for this implementation we are using gcc’ compound expressions. Nevertheless, this feature is only needed for the macros for ordinary arrays, see lines 158 and 204. This is because for the case that the base type is already a VLA we cannot create a compound literal of the base type that would be used as initializer for the elements.

In contrast to that, the base type of a flexible array member may not have a VM Type, and so for the allocation and reallocation macros the address of an initialized compound literal of the base type can be passed into a type-agnostic function, see the code starting at line 254. The only assumption that we need to make for this is that the member that holds the length is a standard integer type other than bool. To code this we use an enumeration type (line 41) that is used by the macro STDC_FLEX_TYPE and another macro STDC_WHEN_SIGNED to determine if the requested array length has a signed or unsigned type.

The declarations of the type-agnostic functions that are then needed in a separate translation unit start in line 58.

#ifndef STDC_INIT_H
#define STDC_INIT_H

#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <threads.h>

/*<!--# xdefine LABEL(C23_COMPATIBILITY) -->*/
#ifdef __has_include
# if __has_include(<stdckdint.h>)
#  include <stdckdint.h>
# endif
#endif

#ifdef __has_c_attribute
# if !__has_c_attribute(__nodiscard__)
#  define __nodiscard__ __gnu__::__warn_unused_result__
# endif
#else
# define __nodiscard__ __gnu__::__warn_unused_result__
#endif

/* If we don't have the header, yet, we may easily emulate it if we
are on a compiler claiming compatibility with gcc. */
#ifndef ckd_add
# ifdef __GNUC__
#  define ckd_add(R, A, B) __builtin_add_overflow ((A), (B), (R))
#  define ckd_sub(R, A, B) __builtin_sub_overflow ((A), (B), (R))
#  define ckd_mul(R, A, B) __builtin_mul_overflow ((A), (B), (R))
# else
#  error "we need a compiler extension for this"
# endif
#endif

/*<!--# xdefine LABEL(ENUM_TYPE) -->*/
enum stdc_flex_type {
  flex_char,
  flex_schar,
  flex_uchar,
  flex_short,
  flex_ushort,
  flex_signed,
  flex_unsigned,
  flex_long,
  flex_ulong,
  flex_llong,
  flex_ullong,
};

typedef enum stdc_flex_type stdc_flex_type;

/*<!--# xdefine LABEL(FUNCTIONS_START) -->*/
#define stdc_delete(P) free((void*)P)

[[__nodiscard__]]
void* stdc_obfuscate(void* p);

void stdc_repeat(size_t n, unsigned char const sample[restrict static n],
                 size_t m, unsigned char buffer[restrict static m]);

[[__nodiscard__]]
void* stdc_init_flex_core_u(unsigned long long len,
                            size_t size_base,
                            stdc_flex_type type, size_t off_len,
                            size_t size_dat, size_t off_dat,
                            unsigned char ret[restrict static (off_dat + len*size_dat)],
                            unsigned char const base_init[restrict static 1],
                            unsigned char const dat_init[restrict static 1]);

[[__nodiscard__]]
void* stdc_init_flex_core_s(signed long long len,
                            size_t size_base,
                            stdc_flex_type type, size_t off_len,
                            size_t size_dat, size_t off_dat,
                            unsigned char ret[restrict static (off_dat + len*size_dat)],
                            unsigned char const base_init[restrict static 1],
                            unsigned char const dat_init[restrict static 1]);

[[__nodiscard__]]
void* stdc_alloc_flex_core_u(unsigned long long len,
                             size_t size_base,
                             stdc_flex_type type, size_t off_len,
                             size_t size_dat, size_t off_dat,
                             unsigned char const base_init[restrict static 1],
                             unsigned char const dat_init[restrict static 1]);

[[__nodiscard__]]
void* stdc_alloc_flex_core_s(signed long long len,
                             size_t size_base,
                             stdc_flex_type type, size_t off_len,
                             size_t size_dat, size_t off_dat,
                             unsigned char const base_init[restrict static 1],
                             unsigned char const dat_init[restrict static 1]);

[[__nodiscard__]]
void* stdc_realloc_flex_core_u(unsigned long long len, void* ostart_,
                               size_t size_base,
                               stdc_flex_type type, size_t off_len,
                               size_t size_dat, size_t off_dat,
                               unsigned char const ini[restrict static 1]);

[[__nodiscard__]]
void* stdc_realloc_flex_core_s(signed long long len, void* ostart_,
                               size_t size_base,
                               stdc_flex_type type, size_t off_len,
                               size_t size_dat, size_t off_dat,
                               unsigned char const ini[restrict static 1]);

/*<!--# xdefine LABEL(OBFUSCATE) -->*/
/* Enable us to reinterpret a given pointer to carry a new object that
  does not alias with any object that we have seen so far. */
#define STDC_OBFUSCATE(X) ((typeof(X)restrict const){ stdc_obfuscate(X), })

/*<!--# xdefine LABEL(HELPERS) -->*/
/*
 Determine element size of target array to which points T. T can be a
 typename of a pointer type or a pointer value. In most cases, the
 result type is just the size each element, unless the pointer is a
 void pointer in which case the size is 1.
*/
#ifdef __GNUC__
#define TARGET_SIZE(T) (sizeof(*(T)))
#else
#define TARGET_SIZE(T)                                                  \
  ({                                                                    \
    auto stargets = (typeof(T))nullptr;                                 \
    ((size_t)_Generic((typeof(*stargets)const volatile*)nullptr,        \
                      void const volatile*: 1,                          \
                      default: sizeof(*stargets)));                     \
  })
#endif

/*
 Determine type of target array to which points T. T can be a typename
 of a pointer type or a pointer value. In most cases, the result type
 is an expression of the same type unless the pointer is a void
 pointer in which case the target type is changed to a unsigned char.
*/
#define TARGET_TYPE(T)                                                  \
  _Generic((typeof(*((typeof(T))nullptr))const volatile*)nullptr,       \
           void const volatile*: (unsigned char*)nullptr,               \
           default: ((typeof(T))nullptr))


// Prior to C23, VLA could not be initialized
#if __STDC_VERSION__ < 202311L
# define STDC_INIT_DEFAULT 0
#else
# define STDC_INIT_DEFAULT /* empty */
#endif

/*<!--# xdefine LABEL(ALLOC) -->*/
/*
 Allocate a target array with a base type as indicated by `T`. `T` can
 be a typename of a pointer type or a pointer value. In most cases,
 the array has the same type unless the pointer is a void pointer in
 which case the array has base type unsigned char.

  stdc_alloc(L)           → STDC_ALLOC_Iplus((L))          → STDC_ALLOC_I((L))        → STDC_ALLOC_IIplus((L), void)
  stdc_alloc(L, T)        → STDC_ALLOC_Iplus((L), T)                                  → STDC_ALLOC_IIplus((L), T)
  stdc_alloc(L, T, INIT)  → STDC_ALLOC_Iplus((L), T, INIT)                            → STDC_ALLOC_IIplus((L), T, INIT)

at least one argument must be provided
*/
/*<!--# xdefine LABEL(ALLOC_END) -->*/
#define stdc_alloc(L, ...)       STDC_ALLOC_Iplus                  ((L) __VA_OPT__(,) __VA_ARGS__)

#define STDC_ALLOC_Iplus(L, ...) STDC_ALLOC_I ## __VA_OPT__(Iplus) (L __VA_OPT__(,) __VA_ARGS__)
#define STDC_ALLOC_I(L)          STDC_ALLOC_IIplus                 (L, void)

/* After default argument insertion, we have at least two arguments. */
#define STDC_ALLOC_IIplus(L, T, ...)                                    \
  ({                                                                    \
    /* captures */                                                      \
    auto const salloc_len = L;                                          \
    typeof(T)* salloc_ret = nullptr;                                    \
    /* code */                                                          \
    auto const salloc_c = TARGET_TYPE(salloc_ret);                      \
    typedef typeof(*salloc_c) salloc_type;                              \
    /* Check if the requested length overflows. */                      \
    if (0 < salloc_len                                                  \
        && salloc_len <= SIZE_MAX                                       \
        /* Check if the requested size overflows. */                    \
        && !ckd_mul(&(size_t){}, (size_t)salloc_len, sizeof(salloc_type))) { \
      salloc_ret = malloc(sizeof(salloc_type[salloc_len]));             \
    } else {                                                            \
      errno = EINVAL;                                                   \
    }                                                                   \
    /* Check if allocation succeeded. */                                \
    if (salloc_ret) {                                                   \
      /* By the constraints, if this is an array, the list is empty.  */ \
      stdc_init(salloc_len, salloc_ret, salloc_type, __VA_ARGS__);      \
    }                                                                   \
    STDC_OBFUSCATE(salloc_ret);                                         \
  })


/*<!--# xdefine LABEL(INIT) -->*/
/*
 Initialize a target array at position P with a base type as indicated
 by T. T can be a typename of a pointer type or a pointer value. In
 most cases, the array has the pointed-to type by T unless the T is a
 void pointer in which case the array has base type unsigned char. P
 will in general be pointer returned by `malloc` or similar function.

 This type-generic macro overwrites repeatedly *P as much contents as
 possible with an initializer of type indicated by T. If there is no
 initializer (... is empty or absent) a default initialized object is
 used. If T is missing, `void*` is used instead.

 This macro also overwrites the effective type of the target
 array. That is, if the target is allocated (has no declaration) the
 initializer is copied into it and the effective type changes to the
 target type.

 The implementation is a bit more complicated because we have to
 take care of implementations that do not yet implement `{}`
 initialization for VLA. */
#define stdc_init(L, P, ...)            STDC_INIT_IIplus((L), (P) __VA_OPT__(,) __VA_ARGS__)

#define STDC_INIT_IIplus(L, P, ...)     STDC_INIT_II  ## __VA_OPT__(Iplus)(L, P        __VA_OPT__(,) __VA_ARGS__)
#define STDC_INIT_IIIplus(L, P, T, ...) STDC_INIT_III ## __VA_OPT__(Iplus)(L, P,    T  __VA_OPT__(,) __VA_ARGS__)
#define STDC_INIT_II(L, P)              STDC_INIT_IIIIplus                (L, P, void, STDC_INIT_DEFAULT)
#define STDC_INIT_III(L, P, T)          STDC_INIT_IIIIplus                (L, P,    T, STDC_INIT_DEFAULT)

/* After default argument insertion, we have four or more arguments. */
#define STDC_INIT_IIIIplus(L, P, T, ...)                                \
  ({                                                                    \
    /* captures */                                                      \
    auto const sinit_len = (L);                                         \
    auto const sinit_p = (typeof(T)*)(P);                               \
    /* code */                                                          \
    auto const sinit_c = TARGET_TYPE(sinit_p);                          \
    typedef typeof(*sinit_c) sinit_type;                                \
    /* Evaluate initializer in the calling context. */                  \
    /* By the constraints, if this is an array, the list is empty. */   \
    /* So this works even if sinit_type is a VLA type. */               \
    /* A compound literal would not offer the same range of functionality. */ \
    sinit_type const sinit_init = { __VA_ARGS__ };                      \
    if (sinit_p) {                                                      \
      stdc_repeat(sizeof(sinit_type), (void const*)&sinit_init,         \
                  sizeof(sinit_type[sinit_len]), (void*)sinit_p);       \
    }                                                                   \
    STDC_OBFUSCATE(sinit_p);                                            \
  })


/*<!--# xdefine LABEL(FLEX) -->*/

#define STDC_FLEX_TYPE(...)                     \
  _Generic((__VA_ARGS__),                       \
           char: flex_char,                     \
           signed char: flex_schar,             \
           unsigned char: flex_uchar,           \
           short: flex_short,                   \
           unsigned short: flex_ushort,         \
           signed: flex_signed,                 \
           unsigned: flex_unsigned,             \
           long: flex_long,                     \
           unsigned long: flex_ulong,           \
           long long: flex_llong,               \
           unsigned long long: flex_ullong)

#define STDC_WHEN_SIGNED(X, A, B)                       \
  _Generic((char(*)[(((typeof(X))-1) < 0)+1]){ },       \
           char(*)[2]: (A),                             \
           char(*)[1]: (B))

/* We have five or more arguments. */
#define stdc_init_flex(L, P, T, FL, LEN, ...)                           \
  ((typeof(T)*restrict const){                                          \
    STDC_WHEN_SIGNED(L, stdc_init_flex_core_s, stdc_init_flex_core_u)   \
    (L, sizeof(T),                                                      \
       STDC_FLEX_TYPE(((typeof(T)*)nullptr)->LEN), offsetof(typeof(T), LEN), \
       sizeof(((typeof(T)*)nullptr)->FL[0]), offsetof(typeof(T), FL),   \
       (void*)(P),                                                      \
       (void const*)&((typeof(T) const){ __VA_ARGS__ }),                      \
       (void const*)&((typeof(((typeof(T)*)nullptr)->FL[0]) const){})), })

#define stdc_alloc_flex(L, T, FL, LEN, ...)                             \
  ((typeof(T)*restrict const){                                          \
    STDC_WHEN_SIGNED(L, stdc_alloc_flex_core_s, stdc_alloc_flex_core_u) \
      (L, sizeof(T),                                                    \
       STDC_FLEX_TYPE(((typeof(T)*)nullptr)->LEN), offsetof(typeof(T), LEN), \
       sizeof(((typeof(T)*)nullptr)->FL[0]), offsetof(typeof(T), FL),   \
       (void const*)&((typeof(T)){ __VA_ARGS__ }),                            \
       (void const*)&((typeof(((typeof(T)*)nullptr)->FL[0])){})), })

#define stdc_realloc_flex(L, P, FL, LEN)                                \
  ((typeof(P)restrict const){                                           \
    STDC_WHEN_SIGNED(L, stdc_realloc_flex_core_s, stdc_realloc_flex_core_u) \
    (L, P, sizeof(*P),                                                  \
     STDC_FLEX_TYPE(((typeof(P))nullptr)->LEN), offsetof(typeof(*(P)), LEN),                     \
     sizeof((P)->FL[0]), offsetof(typeof(*(P)), FL),                    \
     (void const*)&(typeof((P)->FL[0])){}), })


#endif

4.2 The type-agnostic translation unit

This TU must be compile separately and compiler arguments have to be applied such that the compiler cannot guess any action that would be taken by the functions. In particular the auxiliary function stdc_obfucate just acts as a sink for the pointer that it receives as a parameter, the compiler cannot guess whether or not that pointer is kept for later in a secret place. Therefor they cannot omit any action (such as re-initialization) that would be performed on the data. The function then returns the address of a new object that happens to live at the same address. Again, that fact has to remain completely transparent to the compiler; in fact the only use is to initialize a pointer object that is restrict qualified (see the corresponding macro STDC_OBFUSCATE in the header file, line 115)

#include "stdc-init.h"

void stdc_repeat(size_t n, unsigned char const sample[restrict static n],
                 size_t m, unsigned char buffer[restrict static m]) {
  if (m < n) n = m;
  memcpy(buffer, (void const*)sample, n);
  for (; n <= m-n; n *= 2) {
    memcpy(buffer+n, buffer, n);
  }
  if (n < m) {
    memcpy(buffer+n, buffer, m-n);
  }
}

#define ALLOC_CHECK_OR_RETURN(T, M)                             \
  case flex_ ## T: if (len <= M ## _MAX) break; else return ret

#define ALLOC_ASSIGN_BREAK(T, C)                \
  case flex_ ## T: *(C*)lens = len; break

#define ALLOC_RETRIEVE_BREAK(T, C)                \
  case flex_ ## T: olen = *(C*)olens; break

[[__nodiscard__]]
void* stdc_init_flex_core_u(unsigned long long len,
                            size_t size_base,
                            stdc_flex_type type, size_t off_len,
                            size_t size_dat, size_t off_dat,
                            unsigned char ret[restrict static (off_dat + len*size_dat)],
                            unsigned char const base_init[restrict static 1],
                            unsigned char const dat_init[restrict static 1]) {
  size_t size = 0;
  /* Check if the requested size overflows. */
  switch (type) {
    ALLOC_CHECK_OR_RETURN(char, CHAR);
    ALLOC_CHECK_OR_RETURN(uchar, UCHAR);
    ALLOC_CHECK_OR_RETURN(schar, SCHAR);
    ALLOC_CHECK_OR_RETURN(short, SHRT);
    ALLOC_CHECK_OR_RETURN(ushort, USHRT);
    ALLOC_CHECK_OR_RETURN(signed, INT);
    ALLOC_CHECK_OR_RETURN(unsigned, UINT);
    ALLOC_CHECK_OR_RETURN(long, LONG);
    ALLOC_CHECK_OR_RETURN(ulong, ULONG);
    ALLOC_CHECK_OR_RETURN(llong, LLONG);
    ALLOC_CHECK_OR_RETURN(ullong, ULLONG);
  }
  if (!ckd_mul(&size, (size_t)len, size_dat)
      && !ckd_add(&size, size, off_dat)) {
    if (size < size_base) size = size_base;
    if (ret) {
      memcpy(ret, base_init, size_base);
      // write the new length to the offset, all of this should be
      // easily optimized to just one write
      unsigned char* lens = ret+off_len;
      switch (type) {
        ALLOC_ASSIGN_BREAK(char, char);
        ALLOC_ASSIGN_BREAK(uchar, unsigned char);
        ALLOC_ASSIGN_BREAK(schar, signed char);
        ALLOC_ASSIGN_BREAK(short, signed short);
        ALLOC_ASSIGN_BREAK(ushort, unsigned short);
        ALLOC_ASSIGN_BREAK(signed, signed);
        ALLOC_ASSIGN_BREAK(unsigned, unsigned);
        ALLOC_ASSIGN_BREAK(long, signed long);
        ALLOC_ASSIGN_BREAK(ulong, unsigned long);
        ALLOC_ASSIGN_BREAK(llong, signed long long);
        ALLOC_ASSIGN_BREAK(ullong, unsigned long long);
      }
      // initialize new array elements
      stdc_repeat(size_dat, dat_init, len*size_dat, ret+off_dat);
    }
  }
  return ret;
}

[[__nodiscard__]]
void* stdc_init_flex_core_s(signed long long len,
                            size_t size_base,
                            stdc_flex_type type, size_t off_len,
                            size_t size_dat, size_t off_dat,
                            unsigned char ret[restrict static (off_dat + len*size_dat)],
                            unsigned char const base_init[restrict static 1],
                            unsigned char const dat_init[restrict static 1]) {
  return (len < 0)
    ? nullptr
    : stdc_init_flex_core_u(len, size_base, type, off_len, size_dat,
                            off_dat, ret, base_init, dat_init);
}


[[__nodiscard__]]
void* stdc_alloc_flex_core_u(unsigned long long len,
                             size_t size_base,
                             stdc_flex_type type, size_t off_len,
                             size_t size_dat, size_t off_dat,
                             unsigned char const base_init[restrict static 1],
                             unsigned char const dat_init[restrict static 1]) {
  unsigned char* ret = nullptr;
  size_t size = 0;
  /* Check if the requested size overflows. */
  switch (type) {
    ALLOC_CHECK_OR_RETURN(char, CHAR);
    ALLOC_CHECK_OR_RETURN(uchar, UCHAR);
    ALLOC_CHECK_OR_RETURN(schar, SCHAR);
    ALLOC_CHECK_OR_RETURN(short, SHRT);
    ALLOC_CHECK_OR_RETURN(ushort, USHRT);
    ALLOC_CHECK_OR_RETURN(signed, INT);
    ALLOC_CHECK_OR_RETURN(unsigned, UINT);
    ALLOC_CHECK_OR_RETURN(long, LONG);
    ALLOC_CHECK_OR_RETURN(ulong, ULONG);
    ALLOC_CHECK_OR_RETURN(llong, LLONG);
    ALLOC_CHECK_OR_RETURN(ullong, ULLONG);
  }
  if (!ckd_mul(&size, (size_t)len, size_dat)
      && !ckd_add(&size, size, off_dat)) {
    if (size < size_base) size = size_base;
    ret = malloc(size);
    if (ret) {
      memcpy(ret, base_init, size_base);
      // write the new length to the offset, all of this should be
      // easily optimized to just one write
      unsigned char* lens = ret+off_len;
      switch (type) {
        ALLOC_ASSIGN_BREAK(char, char);
        ALLOC_ASSIGN_BREAK(uchar, unsigned char);
        ALLOC_ASSIGN_BREAK(schar, signed char);
        ALLOC_ASSIGN_BREAK(short, signed short);
        ALLOC_ASSIGN_BREAK(ushort, unsigned short);
        ALLOC_ASSIGN_BREAK(signed, signed);
        ALLOC_ASSIGN_BREAK(unsigned, unsigned);
        ALLOC_ASSIGN_BREAK(long, signed long);
        ALLOC_ASSIGN_BREAK(ulong, unsigned long);
        ALLOC_ASSIGN_BREAK(llong, signed long long);
        ALLOC_ASSIGN_BREAK(ullong, unsigned long long);
      }
      // initialize new array elements
      stdc_repeat(size_dat, dat_init, len*size_dat, ret+off_dat);
    }
  }
  return ret;
}

[[__nodiscard__]]
void* stdc_alloc_flex_core_s(signed long long len,
                             size_t size_base,
                             stdc_flex_type type, size_t off_len,
                             size_t size_dat, size_t off_dat,
                             unsigned char const base_init[restrict static 1],
                             unsigned char const dat_init[restrict static 1]) {
  return (len < 0)
    ? nullptr
    : stdc_alloc_flex_core_u(len, size_base, type, off_len, size_dat,
                           off_dat, base_init, dat_init);
}

[[__nodiscard__]]
void* stdc_realloc_flex_core_u(unsigned long long len, void* ostart_,
                               size_t size_base,
                               stdc_flex_type type, size_t off_len,
                               size_t size_dat, size_t off_dat,
                               unsigned char const ini[restrict static 1]) {
  unsigned char* ret = nullptr;
  size_t size = 0;
  /* Check if the requested size overflows. */
  switch (type) {
    ALLOC_CHECK_OR_RETURN(char, CHAR);
    ALLOC_CHECK_OR_RETURN(uchar, UCHAR);
    ALLOC_CHECK_OR_RETURN(schar, SCHAR);
    ALLOC_CHECK_OR_RETURN(short, SHRT);
    ALLOC_CHECK_OR_RETURN(ushort, USHRT);
    ALLOC_CHECK_OR_RETURN(signed, INT);
    ALLOC_CHECK_OR_RETURN(unsigned, UINT);
    ALLOC_CHECK_OR_RETURN(long, LONG);
    ALLOC_CHECK_OR_RETURN(ulong, ULONG);
    ALLOC_CHECK_OR_RETURN(llong, LLONG);
    ALLOC_CHECK_OR_RETURN(ullong, ULLONG);
  }
  if (!ckd_mul(&size, (size_t)len, size_dat)
      && !ckd_add(&size, size, off_dat)) {
    if (size < size_base) size = size_base;
    unsigned char* ostart = ostart_;
    unsigned char* olens = ostart+off_len;
    // read the old length from the offset
    size_t olen = 0;
    switch (type) {
        ALLOC_RETRIEVE_BREAK(char, char);
        ALLOC_RETRIEVE_BREAK(uchar, unsigned char);
        ALLOC_RETRIEVE_BREAK(schar, signed char);
        ALLOC_RETRIEVE_BREAK(short, signed short);
        ALLOC_RETRIEVE_BREAK(ushort, unsigned short);
        ALLOC_RETRIEVE_BREAK(signed, signed);
        ALLOC_RETRIEVE_BREAK(unsigned, unsigned);
        ALLOC_RETRIEVE_BREAK(long, signed long);
        ALLOC_RETRIEVE_BREAK(ulong, unsigned long);
        ALLOC_RETRIEVE_BREAK(llong, signed long long);
        ALLOC_RETRIEVE_BREAK(ullong, unsigned long long);
    }
    if (len < olen) {
      // overwrite old array elements
      stdc_repeat(size_dat, ini, (olen-len)*size_dat, ostart+off_dat+(len*size_dat));
    }
    ret = realloc(ostart, size);
    if (ret) {
      // write the new length to the offset
      unsigned char* lens = ret+off_len;
      switch (type) {
        ALLOC_ASSIGN_BREAK(char, char);
        ALLOC_ASSIGN_BREAK(uchar, unsigned char);
        ALLOC_ASSIGN_BREAK(schar, signed char);
        ALLOC_ASSIGN_BREAK(short, signed short);
        ALLOC_ASSIGN_BREAK(ushort, unsigned short);
        ALLOC_ASSIGN_BREAK(signed, signed);
        ALLOC_ASSIGN_BREAK(unsigned, unsigned);
        ALLOC_ASSIGN_BREAK(long, signed long);
        ALLOC_ASSIGN_BREAK(ulong, unsigned long);
        ALLOC_ASSIGN_BREAK(llong, signed long long);
        ALLOC_ASSIGN_BREAK(ullong, unsigned long long);
      }
      // initialize new array elements
      if (olen < len) {
        stdc_repeat(size_dat, ini, (len-olen)*size_dat, ret+off_dat+(olen*size_dat));
      }
    }
  }
  return ret;
}

[[__nodiscard__]]
void* stdc_realloc_flex_core_s(signed long long len, void* ostart_,
                               size_t size_base,
                               stdc_flex_type type, size_t off_len,
                               size_t size_dat, size_t off_dat,
                               unsigned char const ini[restrict static 1]) {
  return (len < 0)
    ? nullptr
    : stdc_realloc_flex_core_u(len, ostart_, size_base, type,
                               off_len, size_dat, off_dat, ini);
}

[[__nodiscard__]]
void* (stdc_obfuscate)(void* p) {
  return (void*)p;
}