7

Since strict aliasing may help compiler optimize better, C99 introduced the restrict keyword which can be used as a qualifier of a variable if programmers guarantee that it won't be accessed through a pointer to a different type. However, typecasting between different types is inevitable for several reasons and this will make compiler assume that one pointer is not an alias of the other. Therefore, the workaround method is to disable the global strict aliasing optimization by passing -fno-strict-aliasing (a GCC flag). This makes totally no sense because there may only have two pointers that should not be fully optimized. Hence, why not introduce an opposite keyword of restrict which tells compiler that do not assume that those two pointers point to different addresses. This is somewhat similar to what volatile does, and that tells compiler this variable is changed vastly, so treat them in a special way. Is it possible to create such keyword?

EDIT: There is a way to solve this problem. Please see yano's comment below.

Community
  • 1
  • 1
Kevin Dong
  • 5,001
  • 9
  • 29
  • 62
  • 3
    strict aliasing isn't a guarantee that something won't be accessed via more than one pointer -- just that it won't be accessed (generally) through a pointer to a different type. `restrict` is different. – Dmitri Sep 07 '16 at 15:32
  • 1
    Do you have a concrete MVCE? –  Sep 07 '16 at 15:32
  • I am implementing an XOR linked list which will typecast two different types of pointers. This makes GCC be confused and produce segfault excutables without setting `-fno-strict-aliasing` flag. I think that disabling global optimization is such a pity. – Kevin Dong Sep 07 '16 at 15:35
  • 2
    @KevinDong I'd be interested to see the code that produces the error. Would you please post? – cxw Sep 07 '16 at 15:37
  • The code is really huge and complicated currently. It may take me some time to clean it up. ;-(. I may post the MVCE if the cleaning is done. – Kevin Dong Sep 07 '16 at 15:39
  • 1
    Pointer aliases because pointers points to the same object "at the same time". Are you sure that is necessarily the case? E.g. you put aliasing pointers into different scopes so that although they point to the same thing, they don't point to it at the same time? – user3528438 Sep 07 '16 at 15:45
  • @user3528438 It is necessary if implementing an XOR linked list with two sentinel nodes with pointers rather than nodes themselves for reducing memory usage. – Kevin Dong Sep 07 '16 at 15:49
  • 3
    "why not introduce an opposite keyword of restrict which tells compiler that do not assume that those two pointers point to different addresses." --> Is not this the result of not coding `restrict`? – chux - Reinstate Monica Sep 07 '16 at 15:51
  • @KevinDong Perhaps you could use GCC's `__may_alias__` attribute to solve the problem. – Ian Abbott Sep 07 '16 at 15:51
  • @chux Not really. The GCC optimization will produce erroneous code. – Kevin Dong Sep 07 '16 at 15:52
  • So you want a GCC keyword to _selectively_ undo the GCC compiler flag. Maybe a GCC `#pragma` would do? Better than a language addition. – chux - Reinstate Monica Sep 07 '16 at 15:54
  • @IanAbbott Thanks for the information, but it seems not working in my code. ;-( – Kevin Dong Sep 07 '16 at 16:00
  • 1
    @KevinDong I somehow doubt that this is your real problem. I'm strongly suggesting searching your code for undefined behaviour. When talking about an xor list, do you mean a doubly linked list in which the pointer to the previous and next node are stored xor'ed in a single pointer? Do you use `uintptr_t` for that? – Daniel Jour Sep 07 '16 at 17:40
  • @DanielJour Yes. I use `(uintptr_t)(void*)` instead. The problem appears around the list body and the two sentinel nodes which are declared of type `struct node*` rather than `void*`. – Kevin Dong Sep 07 '16 at 17:46
  • A possible solution is to wrap all the pointer types you'll use in a `union`, then use the appropriate member of that `union` at the appropriate time. See http://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule – yano Sep 07 '16 at 18:15
  • @yano Brilliant! This is what I need. ;-) Thanks a lot. – Kevin Dong Sep 07 '16 at 18:23
  • 1
    @yano After some minor fixes, my code can be compiled and execute correctly without `-fno-strict-aliasing` and get some performance enhancement. Thanks again. – Kevin Dong Sep 07 '16 at 18:55
  • 1
    "Therefore, the workaround method is to disable the global strict aliasing optimization" - this is a feature of Standard C, not an optimization. A better workaround is to not write code that breaks the rule. – M.M Sep 07 '16 at 20:58
  • @M.M: The term "C" is used to refer to two languages--a semantically-powerful one invented by Dennis Ritchie, and a semantically-weakened version which has become popular. Given that the semantics of the former have been stable for decades, while the semantics of the latter are being eroded, I favor targeting the former for any code which would benefit from type punning. When using `-fno-strict-aliasing` performance benefit of using the `restrict` qualifier when possible (or cost of failing to use it) is increased, but if code uses `restrict` properly the `-fno-strict-alising` dialect... – supercat Sep 07 '16 at 22:31
  • @M.M: ...will often have baseline performance almost as good as the strict-aliasing dialect; in cases where punning can offer performance gains they can more than offset the losses from -fno-strict-aliasing. – supercat Sep 07 '16 at 22:33

3 Answers3

1

You can use typedef int __attribute__ ((__may_alias__)) int_a; with GCC.
Be careful, -fstrict-aliasing is on by default at GCC -O2 or above.

See gcc.gnu.org for more details.

Michel
  • 259
  • 2
  • 3
  • Interestingly, the code example of the "may_alias" attribute does not seem to hold true with GCC 12: https://gcc.godbolt.org/z/exofqdWon = – Gavin Ray Dec 03 '22 at 18:34
0

There is no opposite of volatile either. Basically the compiler assumes that a variable is not volatile unless you tell it any different.

Same goes for restrict. Unless you tell a compiler that the only way to access a piece of memory is through that pointer, the compiler always must assume that other pointers exists that may point to the same memory location at runtime.

The difference is only that volatile explicitly disables certain kind of optimizations that are naturally performed but could break code if the variable may change behind the back of the compiler noticing it (negative flag), whereas restrict explicitly enables certain kind of optimization that a compile must not otherwise perform (positive flag).

But that's not directly related to strict aliasing. Strict aliasing means to strictly follow the C standard that says "Two pointer of different type pointing to the same memory location is undefined behavior". It's just the case that a C compiler can safely assume, that if two pointers have different type, they cannot alias each other and optimize accordingly, even if there is no restrict keyword. It cannot say anything like that for two pointers of the same type and that's why restrict exists. If you disable strict aliasing, the C compiler will allow this kind of undefined behavior and then the compiler must also treat two pointers of different kind as if they could alias each other, unless you use restrict again.

Here are three examples:

void doStuff ( void * ptr1, void * ptr2 ) {

Can ptr1 and ptr2 both point to the same memory location? Yes, they can!

void doStuff ( void *restrict ptr3, void *restrict ptr4 ) {

Can ptr3 and ptr4 both point to the same memory location? No, they can't! They can't as that would violate what restrict is telling the compiler here.

void doStuff ( int * ptr5, char * ptr6 ) {

Can ptr5 and ptr6 both point to the same memory location? Well, it depends. If strict aliasing is enforced, they can't, because they are of different type. If it is not enforced, they could, despite the C standard saying that this would be undefined behavior but most compilers will just allow it.

Now what would be the purpose of an "anti-restrict"? If strict aliasing is enabled and you would use the anti-restrict in the last sample, you'd break strict aliasing. And whether strict aliasing is enabled or not has no effect on the first two examples. So I fail to see the point of having an anti-restrict. Most of the time you cannot say for sure or know for sure if two pointers might alias each other, so using restrict as a default behavior would break code en masse. It's only that in very rare situations you can guarantee that this is not the case and then you may use restrict to let the compiler know about that, too.

Mecki
  • 125,244
  • 33
  • 244
  • 253
  • The only time ptr3 and ptr4 would not be allowed to point to the same storage would be if some byte of storage that is written via pointer based upon one of them is also accessed (read or written) by a pointer based upon the other. If one has e.g. `int foo(int *restrict p3, int *restrict p4) { p3[0] = 1; p4[1] = 2; }` having both pointers identify the same location in an array would be fine, since the regions of storage accessed using the two pointers don't overlap. – supercat Nov 30 '21 at 20:19
  • @supercat `ptr3` and `ptr3[1]` are not the same pointer. The later one is `ptr3 + 1`, so basically you are using the exiting `ptr3` and you create a new pointer from it. `restrict` applies only to the pointer itself, not to new pointers derived from it. – Mecki Nov 30 '21 at 20:34
  • The meaning of `restrict` is very much tied to pointers that are "based upon" a restrict-qualified pointer. The definition of "based upon" is far too sloppy to really qualify as a "formal specification", but the above function would invoke UB if `p3` were equal to `p4+1`, but have defined behavior if the two pointers were equal to each other. – supercat Nov 30 '21 at 20:44
  • @supercat My examples assume that the pointers are no array pointers and you can only use them as `*ptr` or `ptr[0]` and never anything else. This follows exactly the example 1 given ISO/IEC 9899:1999 (E) on page 110, where the restrict keyword is defined. And yes, aliasing is allowed if the pointer value is never ever modified during program execution but that is something you cannot know when passing a pointer to a function that is not defined with `const` value pointers. – Mecki Nov 30 '21 at 20:51
  • The C Standard expressly specifies that the address of non-array object may be treated as the address of an array of size 1, and this would in turn imply that a object could be accessed via sequence of operations like `q = p+1; q[-1] = whatever; – supercat Dec 06 '21 at 22:18
-1

Given the code:

int foo(int * restrict p)
{
  *p = 3;
  someOutsideFunction();
  return *p;
}

a compiler is entitled to assume that as long as p is in scope, no object which is written using *p will be accessed via any means other than via pointer which is copied or otherwise "derived" from p. Other pointers to the object may exist elsewhere in the universe, but until foo returns none of them will be usable to access *p. Consequently, a compiler could replace the above with

int foo(int * restrict p)
{
  someOutsideFunction();
  *p = 3;
  return 3;
}

since it can see that no pointer copied or derived from p is ever exposed in a way that someOutsideFunction could see it, and thus no legitimate way for someOutsideFunction to access the object *p.

If someOutsideFunction could--unbeknownst to the compiler processing foo--include a keyword that behaved as the opposite of restrict, and could allow it to access the object identified by p [receiving a pointer to that object from a global variable or other such means], that would make it impossible for the compiler processing foo to know whether someOutsideFunction might access *p, and thus impossible for it to know whether it could safely apply the indicated optimization.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • You example is based upon spooky action at a distance but actually even if both pointers are local and you'd modify the memory at `ptr3` in my example, the compiler will not expect the memory of `ptr4` to change; so if you have dereferenced `ptr4` before, the compiler may take the derefenced value still in a register instead of derefencing it again as it would expect it to still be the same value anyway, which isn't true if they are aliasing each other. So no external function call is even required to break code, just read `ptr4`, modify `ptr3`, read `ptr4` again and you may get a wrong value. – Mecki Nov 30 '21 at 21:04
  • @Mecki: In the present language, a compiler wouldn't have to know anything about `someOutsideFunction` to know that it need not allow for the possibility of `*p` being accessed thereby. If, however, there existed a construct that could be used within an external function to override the effects of `restrict`, a compiler would have to allow for the possibility of `someOutsideFunction` accessing `*p` unless it somehow knew that neither that function, nor any function called thereby, could possibly use that construct to access `*p`. – supercat Nov 30 '21 at 23:22