2023-12-13
| document number | date | comment |
|---|---|---|
| n3186 | 202312 | this paper, original proposal |
CC BY, see https://creativecommons.org/licenses/by/4.0
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,
memset or calloc the storage is guaranteed to be initialized as if by default initialization for the indicated type.realloc, partial storage that is returned to the storage system is reinitialized such that no information may leak from one part of a program into another.unsigned char may be used as an arena for application specific memory reinterpretation that is guaranteed to have the necessary effective type as indicated by the caller.const-qualified members are difficult to handle in allocated storage because such a member cannot be written to. The initialization tools provided here make it easy to reinitialize a given storage with new values for const-qualified members; similar to realloc such a reinitialization then creates a new object that incidentally lives within the same storage.Because of these guarantees, to our knowledge currently they cannot be implemented without implementation-specific knowledge. In particular,
memset_explicit.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.
stdc_init feature7.26.2.7 The
stdc_inittype-generic macro
Synopsis
Description
2 The first argument to a invocation to the
stdc_initmacro shall be an assignment expressionLthat has integer type and a value n such that 0 < n ≤SIZE_MAX. The second argument shall be an assignment expressionXPthat has an array type or a pointer type. IfXPhas an array type,Pdenotes the resulting pointer after array-to-pointer conversion; otherwisePis the same asXP. The target type ofPshall not beconstqualified andPshall not be a null pointer constant. If a invocation has a third argument, it shall be a type nameTthat shall either be a void type or a non-atomic complete object type; if no third argument is givenTdefaults tovoid. If four or more arguments are given, the additional arguments shall form an initializer listINITandTshall not be an array type.
3 A call (i.e an invocation that is met during execution) to the
stdc_inittype-generic macro creates a new instance of an array object with an address of valueP; the lifetime of any object that resided in the same storage ends with the call. The new object is initialized as an array ofLelements of typeT, ifTis a complete object type, or as array of bytes as-if by a call tomemset_explicitif it is a void type. All argument expressions including initializers are evaluated at most once. If initializers are specified, then (by the above)Tis 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 callmemset_explicit(P, (INIT), L), respectively. If there is no initializer andTis 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 callmemset_explicit(P, 0, L). The result value and type of the call is(typeof(T)*)Pand refers to the first element of an array object with at leastLmembers.
4 If
Tis 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 isT. IfTis a void type or a non-atomic character type:
- If the storage to which
Prefers is allocated the storage looses any effective type.- Otherwise, if the storage to which
Prefers is defined as an array of a non-atomic character type or is a compound literal of such an array type, the storage looses any effective type.- Otherwise, the initialization only changes the contents but not the effective type.
5 The type of
Pshall betypeof(T)*, implicitly convert to that type, or be a pointer to a non-atomic character type. The storage to whichPrefers shall be mutableFNTx) and be large enough to hold the array object. IfTis a type other than a void type or than a non-atomic character type,Ris the return value of a call andVis an lvalue that is formed that is based onRor any of its copies: if the object created by the call is accessed through an lvalueWwhich is not based onRand 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[]orunsigned char[], they are immutable and thus not suited as storage for a call tostdc_init.
FNTy) Thus, the returned pointer value has all the properties of the evaluation of a pointer object that is
restrictqualified.
Returns
6 If
Tis a complete type, a call to thestdc_inittype-generic macro results in a pointer to the first element of a newly created and properly initialized array object of effective typetypeof(T)[L]which has the same address asP. IfTis a void type, the values of all bytes and the effective type are as indicated.
Example 1
7 A call to
stdc_initcan 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
fthe return value of the call is not used, and thus no lvalue is ever formed. In contrast to that, functionguses the return value and the return expression forms an lvalue that is based onp. Thus an access to the object other than usingp(or a copy) has undefined behavior. An equivalent definitionhuses a compound literal of static storage duration. This enforces that the buffer is not accessible by other means than the pointerq.
Example 2
8 Calls to
stdc_initcan 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
leavesand thus thereturnexpression 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_initcan 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
dpandephave 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.
stdc_alloc feature7.24.3.9 The
stdc_alloctype-generic macro
Synopsis
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 thestdc_inittype-generic macro. Thestdc_allocmacro allocates an object of suitable size as if by a call tomallocand initializes it as an array ofLelements as if by a call tostdc_init.
3 The allocation fails because
Lis outside the required range, the requested object size would have been too large, or because not enough resources are available. If the allocation fails
- If
Lis an integer constant expression, the behavior is undefined.- If
Lis not an integer constant expression, the result is a null pointer value of typetypeof(T)*.FNT1) If it fails becauseLis outside the required range or the requested object size would have been too large,errnois set toEINVAL; if it fails because not enough resources are available it is implementation-defined iferrnois set to an implementation-specific value.
FNT1) That is, if
Lis 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_alloctype-generic macro returns a pointer of typetypeof(T)*refering to the first element of an allocated and properly initialized array object of effective typetypeof(T)[L](ifTis not a void type) orunsigned char[L]. If the allocation fails andLis 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 callstdc_alloc(L, void, UCHAR_MAX)results in a pointer tovoidrefering to an object withLbytes 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(orunsigned charin the case of a void type) and if the allocation is successful, callsstdc_alloc(L),stdc_alloc(L, T), orstdc_alloc(L, T, /*empty*/)are equivalent to the expressions
respectively, only that the type name
Tis evaluated exactly once.
NOTE 3
7 If
Lis 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 replaceLby 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.
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.
stdc_init_flex feature7.26.2.7 The
stdc_init_flextype-generic macro
Synopsis
Description
2 Each invocation of the macro behaves as if a suitable function with a
[[nodiscard]]attribute is called. A call tostdc_init_flexshall have five or more arguments; argumentsL,XP,T, and initializer listINIT(if any) and pointer valuePare determined analogous as forstdc_init, only thatTshall name a structure or union type with a flexible array member. The designation of that memberFLof typeFT[]and the designationLENof an integer member destined to hold the lengthLshall be given as fourth and fifth argument to the call similar to the second argument of theoffsetofmacro. All arguments are evaluated at most once;FLandLENare not evaluated. The valueLshall not be negative and representable in the type of the memberLEN.
3 A call to the
stdc_init_flextype-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 valueP. IfLis greater than zero, the flexible array memberFLis default initialized as an array of typeFT[L]. The memberLENshall have an integer type, possiblyconst-qualified, and is initialized to the valueL. 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_initapply, only that additionally the target storage shall have the capacity to store an object of typeTwithLmembers in the flexible array memberFL, that is it shall have at least a size of the maximum of the two values
Returns
5 A call to the
stdc_init_flextype-generic macro results in a pointer to a newly created object of effective typeTwith a flexible array member with an array lengthLwhich has the same address asP. On return, the storage pointed to byPis initialized and has an effective type ofT; ifLis greater than zero, the storage pointed to by((typeof(T)*)P)->FLis initialized as an array with effective typeFT[L].
Note
6 A call to the
stdc_init_flexmacro is equivalent to a sequence of two combined calls tostdc_initas follows. The first call isstdc_init(1, P, T, .LEN = L, INIT)orstdc_init(1, P, T, .LEN = L)respectively, and initializes the structure or union. The second isstdc_init(L, ((typeof(T)*)P)->FL, FT)and initializes the flexible array member by default initialization. IfLis 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 ofP.
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_flexare valid even though thelenmember isconstqualified;fp->lenandgp->lenreside within the same storage (but not simultaneously) and are correctly initialized to the values100and99, respectively.
stdc_alloc_flex feature7.26.2.7 The
stdc_alloc_flextype-generic macro
Synopsis
Description
2 Each invocation of the macro behaves as if a suitable function with a
[[nodiscard]]attribute is called. A call tostdc_alloc_flexshall 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 thestdc_init_flexmacro. The effect of a call of this macro is the same as by first callingmallocto allocate a suitably large storage and then, if the allocation is successful, callingstdc_init_flexwith the pointer to that storage as second argument.
Returns
3 If the allocation is successful, the
stdc_alloc_flextype-generic macro returns a pointer of typetypeof(T)*to a newly allocated and properly initialized object of effective typeTand ifLis greater than zero such that the flexible array member of that object is a properly initialized array of lengthL. If the allocation fails the result is a null pointer of typetypeof(T)*.
stdc_realloc_flex feature7.26.2.7 The
stdc_realloc_flextype-generic macro
Synopsis
Description
2 Each invocation of the macro behaves as if a suitable function with a
[[nodiscard]]attribute is called. The arguments to a call tostdc_realloc_flexfulfill the same requirements as the first (L), second (XP), fourth (FL), and fifth (LEN) arguments of thestdc_init_flexmacro. A typeTis determined astypeof(*(XP))and shall be a type with a flexible array memberFLand integer memberLEN.
The effect of a call to
stdc_realloc_flexis similar to a call torealloc: a new object with suitable size is allocated if possible. This new object may reside or not at the same address has*XP. IfKis the value of(XP)->LENandMis the minimum ofKandL, data in the union or structure itself and in an initial segment ofMelements of the flexible array member is identical to the data in*XP, with the exception of theLENmember which is set toL. IfLis greater thanKthe trailing elements in the new object at postionsKtoL-1are initialized as if bystdc_init; ifLis less thanKbefore the end of life of*XPthe trailing elements in*XPat postionsLtoK-1are initialized as if bystdc_init.
Returns
3 If the allocation is successful, the
stdc_realloc_flextype-generic macro returns a pointer of typetypeof(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.
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.
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])){}), })
#endifThis 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;
}