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_init
type-generic macro
Synopsis
Description
2 The first argument to a invocation to the
stdc_init
macro shall be an assignment expressionL
that has integer type and a value n such that 0 < n ≤SIZE_MAX
. The second argument shall be an assignment expressionXP
that has an array type or a pointer type. IfXP
has an array type,P
denotes the resulting pointer after array-to-pointer conversion; otherwiseP
is the same asXP
. The target type ofP
shall not beconst
qualified andP
shall not be a null pointer constant. If a invocation has a third argument, it shall be a type nameT
that shall either be a void type or a non-atomic complete object type; if no third argument is givenT
defaults tovoid
. If four or more arguments are given, the additional arguments shall form an initializer listINIT
andT
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 valueP
; the lifetime of any object that resided in the same storage ends with the call. The new object is initialized as an array ofL
elements of typeT
, ifT
is a complete object type, or as array of bytes as-if by a call tomemset_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 callmemset_explicit(P, (INIT), L)
, respectively. If there is no initializer andT
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 callmemset_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 leastL
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 isT
. IfT
is a void type or a non-atomic character type:
- If the storage to which
P
refers is allocated the storage looses any effective type.- Otherwise, if the storage to which
P
refers 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
P
shall betypeof(T)*
, implicitly convert to that type, or be a pointer to a non-atomic character type. The storage to whichP
refers shall be mutableFNTx) and be large enough to hold the array object. IfT
is a type other than a void type or than a non-atomic character type,R
is the return value of a call andV
is an lvalue that is formed that is based onR
or any of its copies: if the object created by the call is accessed through an lvalueW
which is not based onR
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[]
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
restrict
qualified.
Returns
6 If
T
is a complete type, a call to thestdc_init
type-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
. IfT
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, functiong
uses 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 definitionh
uses 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_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 thereturn
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
andep
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.
stdc_alloc
feature7.24.3.9 The
stdc_alloc
type-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_init
type-generic macro. Thestdc_alloc
macro allocates an object of suitable size as if by a call tomalloc
and initializes it as an array ofL
elements as if by a call tostdc_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
- If
L
is an integer constant expression, the behavior is undefined.- If
L
is not an integer constant expression, the result is a null pointer value of typetypeof(T)*
.FNT1) If it fails becauseL
is outside the required range or the requested object size would have been too large,errno
is set toEINVAL
; if it fails because not enough resources are available it is implementation-defined iferrno
is set to an implementation-specific value.
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 typetypeof(T)*
refering to the first element of an allocated and properly initialized array object of effective typetypeof(T)[L]
(ifT
is not a void type) orunsigned char[L]
. If the allocation fails andL
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 callstdc_alloc(L, void, UCHAR_MAX)
results in a pointer tovoid
refering to an object withL
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
(orunsigned char
in 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
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 replaceL
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.
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_flex
type-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_flex
shall have five or more arguments; argumentsL
,XP
,T
, and initializer listINIT
(if any) and pointer valueP
are determined analogous as forstdc_init
, only thatT
shall name a structure or union type with a flexible array member. The designation of that memberFL
of typeFT[]
and the designationLEN
of an integer member destined to hold the lengthL
shall be given as fourth and fifth argument to the call similar to the second argument of theoffsetof
macro. All arguments are evaluated at most once;FL
andLEN
are not evaluated. The valueL
shall not be negative and representable in the type of the memberLEN
.
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 valueP
. IfL
is greater than zero, the flexible array memberFL
is default initialized as an array of typeFT[L]
. The memberLEN
shall 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_init
apply, only that additionally the target storage shall have the capacity to store an object of typeT
withL
members 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_flex
type-generic macro results in a pointer to a newly created object of effective typeT
with a flexible array member with an array lengthL
which has the same address asP
. On return, the storage pointed to byP
is initialized and has an effective type ofT
; ifL
is greater than zero, the storage pointed to by((typeof(T)*)P)->FL
is initialized as an array with effective typeFT[L]
.
Note
6 A call to the
stdc_init_flex
macro is equivalent to a sequence of two combined calls tostdc_init
as 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. IfL
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 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_flex
are valid even though thelen
member isconst
qualified;fp->len
andgp->len
reside within the same storage (but not simultaneously) and are correctly initialized to the values100
and99
, respectively.
stdc_alloc_flex
feature7.26.2.7 The
stdc_alloc_flex
type-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_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 thestdc_init_flex
macro. The effect of a call of this macro is the same as by first callingmalloc
to allocate a suitably large storage and then, if the allocation is successful, callingstdc_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 typetypeof(T)*
to a newly allocated and properly initialized object of effective typeT
and ifL
is 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_flex
type-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_flex
fulfill the same requirements as the first (L
), second (XP
), fourth (FL
), and fifth (LEN
) arguments of thestdc_init_flex
macro. A typeT
is determined astypeof(*(XP))
and shall be a type with a flexible array memberFL
and integer memberLEN
.
The effect of a call to
stdc_realloc_flex
is 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
. IfK
is the value of(XP)->LEN
andM
is the minimum ofK
andL
, data in the union or structure itself and in an initial segment ofM
elements of the flexible array member is identical to the data in*XP
, with the exception of theLEN
member which is set toL
. IfL
is greater thanK
the trailing elements in the new object at postionsK
toL-1
are initialized as if bystdc_init
; ifL
is less thanK
before the end of life of*XP
the trailing elements in*XP
at postionsL
toK-1
are initialized as if bystdc_init
.
Returns
3 If the allocation is successful, the
stdc_realloc_flex
type-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])){}), })
#endif
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;
}