Opposite keyword of "restrict" in C?
Asked Answered
A

3

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.

Adust answered 7/9, 2016 at 15:29 Comment(20)
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.Alexandriaalexandrian
Do you have a concrete MVCE?Coed
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.Adust
@KevinDong I'd be interested to see the code that produces the error. Would you please post?Deadline
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.Adust
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?Titanism
@Titanism It is necessary if implementing an XOR linked list with two sentinel nodes with pointers rather than nodes themselves for reducing memory usage.Adust
"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?Selfoperating
@KevinDong Perhaps you could use GCC's __may_alias__ attribute to solve the problem.Armful
@chux Not really. The GCC optimization will produce erroneous code.Adust
So you want a GCC keyword to selectively undo the GCC compiler flag. Maybe a GCC #pragma would do? Better than a language addition.Selfoperating
@IanAbbott Thanks for the information, but it seems not working in my code. ;-(Adust
@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?Knossos
@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*.Adust
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 #99150Yet
@Yet Brilliant! This is what I need. ;-) Thanks a lot.Adust
@Yet After some minor fixes, my code can be compiled and execute correctly without -fno-strict-aliasing and get some performance enhancement. Thanks again.Adust
"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.Flora
@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...Bronson
@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.Bronson
Q
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.

Quizmaster answered 1/7, 2022 at 0:40 Comment(1)
Interestingly, the code example of the "may_alias" attribute does not seem to hold true with GCC 12: gcc.godbolt.org/z/exofqdWon =Deodorize
D
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.

Deposition answered 29/11, 2021 at 17:12 Comment(5)
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.Bronson
@Bronson 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.Deposition
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.Bronson
@Bronson 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.Deposition
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;Bronson
B
-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.

Bronson answered 10/3, 2017 at 17:44 Comment(2)
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.Deposition
@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.Bronson

© 2022 - 2025 — McMap. All rights reserved.