Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

restrict "corner cases"

68 views
Skip to first unread message

Ian Pilcher

unread,
Mar 19, 2021, 11:33:21 AM3/19/21
to
It took me years, but I've finally managed to grok aliasing and the
purpose of the restrict qualifier.

I believe that I know the basic aliasing rules, but I often find myself
wondering if restrict is necessary in certain situations. For example:

uint8_t ipmi_cmd(const uint8_t *const cmd, const size_t cmd_len,
uint8_t *const response, const size_t resp_len)

Can the compiler infer from this that cmd and response do not alias one
another (because cmd is a pointer to *const* uint8_t and response is a
pointer to non-const uint8_t)?

Are pointers to uint8_t treated as pointers to char for aliasing
purposes?

Does anyone know of a resource anywhere that discusses these sorts of
"corner cases?"

--
========================================================================
Ian Pilcher arequ...@gmail.com
-------- "I grew up before Mark Zuckerberg invented friendship" --------
========================================================================

Bonita Montero

unread,
Mar 19, 2021, 11:37:04 AM3/19/21
to
>   uint8_t ipmi_cmd(const uint8_t *const cmd, const size_t cmd_len,
>                    uint8_t *const response, const size_t resp_len)

These consts mean logical constness and not physical constness only.
So the compiler has to assume aliasing here.

David Brown

unread,
Mar 19, 2021, 1:03:18 PM3/19/21
to
On 19/03/2021 16:33, Ian Pilcher wrote:
> It took me years, but I've finally managed to grok aliasing and the
> purpose of the restrict qualifier.
>
> I believe that I know the basic aliasing rules, but I often find myself
> wondering if restrict is necessary in certain situations.  For example:

"restrict" is never necessary for code functionality. It gives the
compiler extra information for optimisation (and, if you are lucky,
static error checking), but no program that is correct /with/ restrict
will change its behaviour if restrict is omitted.

>
>   uint8_t ipmi_cmd(const uint8_t *const cmd, const size_t cmd_len,
>                    uint8_t *const response, const size_t resp_len)
>

Putting "const" on the parameters themselves doesn't do anything to the
code, or give the compiler any more information, as the parameters are
always copied by value. It simply means you promise not to change the
local copy in the function body. Many programmers like to avoid
changing parameters in the function body, except perhaps in a few simple
or idiomatic cases. Putting "const" on these means the compiler will
complain if you break that rule. But it also may make it harder to see
the only relevant "const" in that declaration - the first one on "cmd".

> Can the compiler infer from this that cmd and response do not alias one
> another (because cmd is a pointer to *const* uint8_t and response is a
> pointer to non-const uint8_t)?

No. The "const" in a pointer declaration (including a parameter) is
only your promise not to change the value via that pointer, and even
that is not a binding promise. Such "const" are useful for making the
code clear and helping the compiler spot (and warn about) some kinds of
errors, but it is not strong enough to give the kind of optimisation
benefits you are hoping for. So "restrict" will help here.

(If you are using gcc 10 or newer, and are happy to use extensions, you
can also use the "access" function attributes to give the compiler more
details about how data is accessed via pointers. This could be used for
better error checking and/or optimisation. Other compilers may have
corresponding features.)

>
> Are pointers to uint8_t treated as pointers to char for aliasing
> purposes?

Hypothetically, "uint8_t" may not necessarily be a "char" type. I have
not heard of any real compilers where it is not, however.

>
> Does anyone know of a resource anywhere that discusses these sorts of
> "corner cases?"
>

This newsgroup specialises in such "language lawyer" details, so this is
probably as good a place as any. Of course, the C language standards
are important references, and the <https://en.cppreference.com/w/c>
website is a convenient place to look.

Kaz Kylheku

unread,
Mar 19, 2021, 1:19:54 PM3/19/21
to
On 2021-03-19, Ian Pilcher <arequ...@gmail.com> wrote:
> It took me years, but I've finally managed to grok aliasing and the
> purpose of the restrict qualifier.
>
> I believe that I know the basic aliasing rules, but I often find myself
> wondering if restrict is necessary in certain situations. For example:
>
> uint8_t ipmi_cmd(const uint8_t *const cmd, const size_t cmd_len,
> uint8_t *const response, const size_t resp_len)
>
> Can the compiler infer from this that cmd and response do not alias one
> another (because cmd is a pointer to *const* uint8_t and response is a
> pointer to non-const uint8_t)?

No, because const int and plain int are compatible types, which may
be aliased.

A const int * pointer is not required to point only to an object
that was defined const.

The const just means that those aliases which carry the qualifier may
not be used for modifying the referenced object.

In a scope where there is an object that is *defined* const, the
implementation may assume that no assignment expression of any type
modifies that object's value.

> Are pointers to uint8_t treated as pointers to char for aliasing
> purposes?

uint8_t is not required to be a typedef name for a character type.

Obviously, if it is a typedef name for, say, unsigned char, then in that
implementation, you can reason about it that way.


--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal

Ben Bacarisse

unread,
Mar 19, 2021, 8:41:08 PM3/19/21
to
Kaz Kylheku <563-36...@kylheku.com> writes:

> On 2021-03-19, Ian Pilcher <arequ...@gmail.com> wrote:
>> It took me years, but I've finally managed to grok aliasing and the
>> purpose of the restrict qualifier.
>>
>> I believe that I know the basic aliasing rules, but I often find myself
>> wondering if restrict is necessary in certain situations. For example:
>>
>> uint8_t ipmi_cmd(const uint8_t *const cmd, const size_t cmd_len,
>> uint8_t *const response, const size_t resp_len)
>>
>> Can the compiler infer from this that cmd and response do not alias one
>> another (because cmd is a pointer to *const* uint8_t and response is a
>> pointer to non-const uint8_t)?
>
> No, because const int and plain int are compatible types, which may
> be aliased.

Actually they are not compatible types, but the aliasing rules
specifically allow it, so your conclusion is correct.

--
Ben.

Keith Thompson

unread,
Mar 19, 2021, 8:42:46 PM3/19/21
to
David Brown <david...@hesbynett.no> writes:
> On 19/03/2021 16:33, Ian Pilcher wrote:
[...]
>> Are pointers to uint8_t treated as pointers to char for aliasing
>> purposes?
>
> Hypothetically, "uint8_t" may not necessarily be a "char" type. I have
> not heard of any real compilers where it is not, however.

Right. For uint8_t to be something other than a character type, it
would have to be an extended integer type, since no predefined integer
type other than the character types can be narrower than 16 bits. A
conforming implementation with CHAR_BIT>8 could not define an extended
integer type narrower than CHAR_BIT bits, and could not define uint8_t.

I've never even heard of a C implementation that provides any extended
integer types. (gcc's __int128 and unsigned __int128 do not, I believe,
meet the standard's requirements for extended integer types.)

--
Keith Thompson (The_Other_Keith) Keith.S.T...@gmail.com
Working, but not speaking, for Philips Healthcare
void Void(void) { Void(); } /* The recursive call of the void */

Kaz Kylheku

unread,
Mar 19, 2021, 9:07:29 PM3/19/21
to
Sorry, yes, right; but other situations also. For instance:

int f(const int);
int f(int);

are compatible, equivalent declarations. There, the qualifier also
doesn't matter.

David Brown

unread,
Mar 20, 2021, 10:14:48 AM3/20/21
to
On 20/03/2021 01:42, Keith Thompson wrote:
> David Brown <david...@hesbynett.no> writes:
>> On 19/03/2021 16:33, Ian Pilcher wrote:
> [...]
>>> Are pointers to uint8_t treated as pointers to char for aliasing
>>> purposes?
>>
>> Hypothetically, "uint8_t" may not necessarily be a "char" type. I have
>> not heard of any real compilers where it is not, however.
>
> Right. For uint8_t to be something other than a character type, it
> would have to be an extended integer type, since no predefined integer
> type other than the character types can be narrower than 16 bits. A
> conforming implementation with CHAR_BIT>8 could not define an extended
> integer type narrower than CHAR_BIT bits, and could not define uint8_t.
>
> I've never even heard of a C implementation that provides any extended
> integer types. (gcc's __int128 and unsigned __int128 do not, I believe,
> meet the standard's requirements for extended integer types.)
>

That is correct - although to be honest, I can't remember what they are
missing. There is no way to write literals of type __int128 in gcc
(unless "long long int" is 128 bits already), which may be the sticking
point. There is also no printf formats for them, if that is a
requirement. And therefore int_max_t is typically 64-bit even though
the compiler has these 128 bit types (on 64-bit implementations).


But I know of one complicating case. The avr is an 8-bit target
supported by gcc. It is generally used with its own specific library
avrlibc rather than a generic one such as newlib (a common choice for
microcontrollers). Here int8_t and uint8_t are defined as:

typedef signed int int8_t __attribute__((__mode__(__QI__)));
typedef unsigned int uint8_t __attribute__((__mode__(__QI__)));

This uses a gcc-specific extension attribute to say "int8_t is an int
that is 8 bits wide".

That mechanism could have been used to make "uint8_t" an unsigned
integer type without also having the special aliasing abilities (and
optimisation limitations) of unsigned char.

A few tests on generated code, however, suggest that gcc treats these
exactly as signed char and unsigned char, both in how the react to
_Generic and in how the compiler handles them for alias analysis.

(I still don't know why the avr gcc port and/or avrlibc defines the
types in this way.)



Ian Pilcher

unread,
Mar 23, 2021, 4:10:29 PM3/23/21
to
Here's another one:

void foo(struct bar *baz, char *name, void *data)

With this prototype, I'm 99% sure that the compiler has to assume that
name may alias either baz or data. Since I know that this is not the
case, I change the prototype to:

void foo(struct bar *baz, char *restrict name, void *data)

What about data, though? I know that it doesn't alias baz or name;
should I add restrict to its declaration?

Kaz Kylheku

unread,
Mar 23, 2021, 7:46:19 PM3/23/21
to
On 2021-03-23, Ian Pilcher <arequ...@gmail.com> wrote:
> Here's another one:
>
> void foo(struct bar *baz, char *name, void *data)
>
> With this prototype, I'm 99% sure that the compiler has to assume that
> name may alias either baz or data. Since I know that this is not the
> case, I change the prototype to:
>
> void foo(struct bar *baz, char *restrict name, void *data)
>
> What about data, though? I know that it doesn't alias baz or name;
> should I add restrict to its declaration?

Yes; if there are N pointer parameters, and you want to express that
no pair of them alias, you have to use at least N-1 restrict qualifiers.

If you use only N-2 qualifiers, then it means you have two pointers
without restrict, and it would be valid for those two to alias.

For any pair P, Q of pointers that are not to alias, at least one
of them has to be restrict qualified.

Tim Rentsch

unread,
Mar 28, 2021, 9:31:13 AM3/28/21
to
Ian Pilcher <arequ...@gmail.com> writes:

> It took me years, but I've finally managed to grok aliasing and
> the purpose of the restrict qualifier.
>
> I believe that I know the basic aliasing rules, but I often find
> myself wondering if restrict is necessary in certain situations.
> For example:
>
> uint8_t ipmi_cmd(const uint8_t *const cmd, const size_t cmd_len,
> uint8_t *const response, const size_t resp_len)
>
> Can the compiler infer from this that cmd and response do not
> alias one another (because cmd is a pointer to *const* uint8_t and
> response is a pointer to non-const uint8_t)?
>
> Are pointers to uint8_t treated as pointers to char for aliasing
> purposes?
>
> Does anyone know of a resource anywhere that discusses these sorts
> of "corner cases?"

If you are sure that there will never be a case where any memory
accessed through one argument will be accessed through any of the
others, the safest thing is to use 'restrict' for all the pointer
parameters. Here "safest" is meant in the sense of making it
easy for the compiler not to be misled about potential aliasing
issues.

Ian Pilcher

unread,
Mar 31, 2021, 11:44:28 AM3/31/21
to
On 3/23/21 6:46 PM, Kaz Kylheku wrote:
> On 2021-03-23, Ian Pilcher <arequ...@gmail.com> wrote:
>> Here's another one:
>>
>> void foo(struct bar *baz, char *name, void *data)
>>
>> With this prototype, I'm 99% sure that the compiler has to assume that
>> name may alias either baz or data. Since I know that this is not the
>> case, I change the prototype to:
>>
>> void foo(struct bar *baz, char *restrict name, void *data)
>>
>> What about data, though? I know that it doesn't alias baz or name;
>> should I add restrict to its declaration?
>
> Yes; if there are N pointer parameters, and you want to express that
> no pair of them alias, you have to use at least N-1 restrict qualifiers.

But that's not entirely true. For example:

void foo(int *i, double *d)

In this case, the compiler will assume that i and d cannot alias one
another, because they point to incompatible types of data.

So one generally doesn't need to use restrict when only passing pointers
to incompatible types. Character types, however, are an exception to
this rule.

void foo(int *i, double *d, char *c)

The compiler must assume that c may alias either i or d. So if I know
that this is not the case, I would change the prototype to:

void foo(int *i, double *d, char *restrict c)

So what about void pointers?

void foo(int *i, double *d, void *v)

Can the compiler assume that v does not alias i or d in this case, or
do I need to add restrict?

Andrey Tarasevich

unread,
Apr 2, 2021, 10:44:40 PM4/2/21
to
On 3/31/2021 8:44 AM, Ian Pilcher wrote:
>
> But that's not entirely true.  For example:
>
>   void foo(int *i, double *d)
>
> In this case, the compiler will assume that i and d cannot alias one
> another, because they point to incompatible types of data.
>

True, but what about situations when they both point to members of the
same union? Considering that type-punning through unions is permissible.

--
Best regards,
Andrey Tarasevich

Kaz Kylheku

unread,
Apr 3, 2021, 12:21:05 PM4/3/21
to
"Through pointers" isn't "through a union", though.

Through a union means that we assign an lvalue like pu->memb0
or u.memb0, and then access one like pu->memb1 or u.memb1.
0 new messages