Document Number: N2484
Submitter: Aaron Peter
Bachmann
Submission Date: 2020-02-19
Make pointer type casting useful without negatively impacting
performance
Pointer-type-punning is useful but mostly
undefined behavior according to the standards ISO/IEC 9899:1999
... ISO/IEC 9899:2018.
Before that there were implementation defined aspects. Therefore
we have to resort to additional - non-portable - extensions
compiler normally provide or use another programming language
(often assembler) or work-arounds to achieve our intended goals
as programmers. Strict aliasing rules are also useful. They
allow for more efficient code without extra work for the
programmer. We can allow type-punning in many cases without
negatively impacting performance.
memset()
, memcpy()
,
strlen()
, strcpy()
, ... malloc()
and friends shall
be implementable in plain Cmemcpy()
is a canonical
righteous way to deal with the representation of
objects, but it has its problemsmemcpy()
introduced. The programs become harder to read,
the number of source lines increases, the programs
become less efficient in terms of runtime, code-size,
stack-consumption, latency.
memcpy()
cannot reasonably be used in an
implementation of memcpy()
.
memcpy()
, then memcpy()
must not reside in the same flash - often the only
one. If code and data are both in RAM they may compete
for the same bus introducing additional inefficiency.
memcpy()
which
has to be able to copy objects of arbitrary type.
restrict
.
-fno-strict-aliasing
.
Some C-compilers assume no strict aliasing either by default or
as the only option: pcc, tcc, the original C-compiler by DMR,
Microsoft C-compilers, ... -fno-strict-aliasing
.via
attribute((__may_alias__))
Make object-pointer-casting (casting a
pointer-type to a pointer to a different type) valid
and well defined in a local scope, i. e. function-scope and
block-scopes within a function, provided that the value of the
pointer derived from the original pointer via the cast to a
fundamentally different pointer does not escape the scope. The
accesses via this pointer shall be valid as well, provided we
honor other restrictions (const, alignment, ...).
#include <limits.h>
void f1(float **f, unsigned **u){
_Static_assert(sizeof(float)==sizeof(unsigned),"precondition
violated");
_Static_assert(4==sizeof(unsigned),"precondition violated");
_Static_assert(8==CHAR_BIT,"precondition
violated");
unsigned u32;
#ifdef DO_SOMETING_INVALID
*f=*u; // invalid
*u=*f; // invalid
*f=*(float**)u; //
invalid
*u=*(unsigned**)f; // invalid
#else
(void)u;
#endif
u32=**(unsigned**)f;
u32^=1u<<31;
// under the
restrictions given above a compiler not seeing the
implemention
// of the
function but its prototype only must already assume
// **f may be
changed (as float); in this case **f=-**f
**f=(float)u32;
// valid according to the proposal
}
#include <string.h>
#include <limits.h>
#include <stdint.h>
#define ALIGN (sizeof(size_t))
#define ONES ((size_t)-1/UCHAR_MAX) // 0x01010101 for 32 bit integer
#define HIGHS (ONES * (UCHAR_MAX/2+1)) // 0x80808080 for 32 bit integer
#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) // only 0 has OV & high bit 0
size_t strlen(const char *s){
const char *a = s;
const size_t *w;
for (; (uintptr_t)s % ALIGN; s++) if (!*s) return s-a;
// harmless but undefined, because we eventually read more than object-size!
#ifdef STRLEN_USE_MEMCOPY
// pray compiler will remove memcpy()
for (;memcpy(&w,s,sizeof(size_t)), !HASZERO(*w); s+=sizeof(size_t));
#else
for (w = (const void *)s; !HASZERO(*w); w++);
// code matching this proposal
s = (const void *)w;
#endif
for (; *s; s++);
return s-a;
}
static float *Fp=(float*)&Something;
static unsigned *Uu
=(unsigned*)
&Something; // invalid
for example:
For portable programs [u]int32_t
must be assumed to alias any integer type, except
for long long
, since it could be int
or long
and almost any other integer-type.
short
can have 32 bits. The same applies
for other [u]intx_t
-types.malloc()
. For that we have to
allow aliasing in other situations as well. This too may
be possible without significantly restricting the
usefulness of strict aliasing. This bullet point only
sketches a potential loophole. char*
.
If we also allow to access any object of char*
via any_type*
we can implement malloc()
and friends. This would still not allow to alias
any_type1*
with any_type2*
provided any_type1*
and any_type2*
are mutually different and different from char*
.
Within the lifetime of the object pointed to by char*
we would allow char*
to alias any_type1*
,
any_type2*
,... but the use of the aliases any_type1*
,
any_type2*
,... must be sequenced.
An object shall have its stored value accessed only by an lvalue
expression that has one of the following types:89)