In this blog post, you will learn the concept of the restrict keyword in C programming. You will also learn how to use the restrict Qualifiers with identifiers and their effect on them. But before describing the restrict qualifier want to discuss the C-type qualifiers. For better understanding, we will also see some programming examples of “restrict keyword”.
Type qualifier is a keyword that is applied to a type, resulting in a qualified type. Some C qualifiers are const, restrict (C99), volatile, and _Atomic (C11).
The const keyword is compiler-enforced and says that the program could not change the value of the object that means it makes the object a nonmodifiable type. For example, const int data
is a qualified type representing a constant integer, where const is a type qualifier and we can not modify the value of “data”.
What is restrict qualifier (or restrict keyword)?
The restrict type qualifier, introduced in C99 and it is a special type qualifier and can be applied to pointer declarations. It qualifies the pointer, not what it points at. An object that is accessed through a restrict-qualified pointer has a special association with that pointer.
Basically, restrict is an optimization hint to the compiler that no other pointer in the current scope refers to the same memory location. That is, only the pointer
or a value derived from it such as pointer + 1
is used to access the object during the lifetime of the pointer. This helps the compiler produce more optimized code. See the below programming examples for a better understanding.
Programming Examples using the restrict keyword:
Let’s see an example to understand how to restrict keywords optimize the code
. Let consider the below function. The configuration which I am using, compiler x86-64 gcc (trunk)
with settings -std=c17 -O3
.
Case 1: function with restrict keyword:
void copyArray(int n, int * restrict p, int * restrict q) { while (n-- > 0) { *p++ = *q++; } }
The compiler generates below assembly code:
copyArray: movslq %edi, %rax movq %rsi, %rdi movq %rdx, %rsi testl %eax, %eax jle .L1 leaq 0(,%rax,4), %rdx jmp memcpy .L1: ret
Case 2: function without restrict keyword:
Now remove the restrict keyword from the function and check the assembly code generated by the compiler with the same configuration.
void copyArray(int n, int *p, int *q) { while (n-- > 0) { *p++ = *q++; } }
The compiler generates below assembly code without the restrict keyword. You can see that code is less optimized.
copyArray: movl %edi, %r8d movq %rsi, %rcx leal -1(%rdi), %edi testl %r8d, %r8d jle .L1 leaq 4(%rdx), %rsi movq %rcx, %rax subq %rsi, %rax cmpq $8, %rax jbe .L3 cmpl $2, %edi jbe .L3 movl %r8d, %esi xorl %eax, %eax shrl $2, %esi salq $4, %rsi .L4: movdqu (%rdx,%rax), %xmm0 movups %xmm0, (%rcx,%rax) addq $16, %rax cmpq %rsi, %rax jne .L4 movl %r8d, %esi andl $-4, %esi movl %esi, %eax subl %esi, %edi salq $2, %rax addq %rax, %rcx addq %rdx, %rax andl $3, %r8d je .L1 movl (%rax), %edx movl %edx, (%rcx) testl %edi, %edi jle .L1 movl 4(%rax), %edx movl %edx, 4(%rcx) cmpl $1, %edi jle .L1 movl 8(%rax), %eax movl %eax, 8(%rcx) ret .L3: movslq %r8d, %rsi xorl %eax, %eax .L6: movl (%rdx,%rax,4), %edi movl %edi, (%rcx,%rax,4) addq $1, %rax cmpq %rsi, %rax jne .L6 .L1: ret
Note:
A translator is free to ignore any or all aliasing implications of uses of restrict.
Some important points related to the restrict qualifier:
1.
The intended use of the restrict qualifier (like the register storage class) is to promote optimization.
2.
If a pointer p
qualify with restrict, it tells the compiler that pointer p
is the only way to access the object pointed by it in the current scope.
int data = 12; { int* restrict p1 = &data; int* p2 = &data; // undefined behavior }
3.
Assignment from one restricted pointer to another is undefined behavior within the same scope.
void fun() { int* restrict p1 = &data1; int* restrict p2 = &data2; p1 = p2; // undefined behavior /*Doing some work*/ }
4.
The benefit of the restrict qualifiers is that they enable a translator to make an effective dependence analysis of function without examining any of the calls of the function in the program. But it is the responsibility of the programmer to examine all of those calls to ensure that none give undefined behavior.
See the below example, the second call of fun() in test() has undefined behavior because each of arr[1]
through arr[49]
is accessed through both p
and q
.
void fun(int n, int * restrict p, int * restrict q) { while(n-- > 0) { *p++ = *q++; } } void test(void) { extern int arr[100]; fun(50, arr + 50, arr); // Valid fun(50, arr + 1, arr); /* Undefined behavior: arr[1] to arr[49] is accessed through both p and q in fun() */ }
So if you are using the restrict in your function parameters you must examine each call of the function. Because it might give undefined behavior with improper inputs.
5.
You can alias an unmodified object through two restricted pointers. See the below example.
void fun(int n, int * restrict p, int * restrict q, int * restrict r) { int i; for (i = 0; i < n; i++) { p[i] = q[i] + r[i]; } }
In particular, if a and b are disjoint arrays, a call of the form fun(100, a, b, b)
has defined behavior, because array “b” is not modified within function fun().
6.
A translator is free to ignore any or all aliasing implications of uses of restrict. It means the compiler is free to ignore the restrict keyword.
7.
We know that assignment from one restricted pointer to another is undefined behavior within the same scope. But here is one exception, ‘‘outer-to-inner’’ assignments between restricted pointers declared in nested blocks have defined behavior.
See the below example where we are declaring a new restricted pointer based on the original one. So there is no UB (undefined behavior).
{ int * restrict p1; int * restrict q1; p1 = q1; // undefined behavior { int * restrict p2 = p1; // valid int * restrict q2 = q1; // valid p1 = q2; // undefined behavior p2 = q2; // undefined behavior } }
8.
Restricted pointers can be assigned to unrestricted pointers freely.
For example,
void fun(int n, float * restrict r, float * restrict s) { float * p = r, * q = s; // OK while(n-- > 0) { *p++ = *q++; } }
The lvalue *p
that is used to access the underlying array pointed by r
has its address based on r
. In another word you can say that *p
is indirect access of underlying array through r
. It is similar for *q
. Because all the accesses occur, even if indirectly, through the originally restricted pointers. So no undefined behavior.
Recommended Post
- C Programming Courses And Tutorials.
- C Type Specifiers with Programming Examples.
- Punctuators in C.
- Elements of C language.
- C String Literals with Its Types
- C identifiers and naming rules.
- Stringizing operator (#) in C
- Token Pasting Operator in C/C++ programming.
- Learn how to use the typedef in C.
- Macro in C, you should know.
- enum in C,7 application.
- You should know the volatile Qualifier.
- C format specifiers.