Why does the restrict qualifier still allow memcpy to access overlapping memory?

2.6k Views Asked by At

I wanted to see if restrict would prevent memcpy from accessing overlapping memory.

The memcpy function copies n bytes from memory area src to memory area dest directly. The memory areas should not overlap.

memmove uses a buffer so there is no risk of overlapping memory.

The restrict qualifier says that for the lifetime of the pointer, only the pointer itself or a value directly from it (such as pointer + n) will have access to the data of that object. If the declaration of intent is not followed and the object is accessed by an independent pointer, this will result in undefined behavior.

#include <stdio.h>
#include <string.h>

#define SIZE 30

int main ()
{
    char *restrict itself;
    itself = malloc(SIZE);
    strcpy(itself, "Does restrict stop undefined behavior?");
    printf("%d\n", &itself);
    memcpy(itself, itself, SIZE);
    puts(itself);
    printf("%d\n", &itself);

    memcpy(itself-14, itself, SIZE); //intentionally trying to access restricted memory
    puts(itself);
    printf("%d\n", &itself);

    return (0);
}

Output ()

Address of itself: 12345
Does restrict stop undefined behavior?
Address of itself: 12345
stop undefined bop undefined behavior?
Address of itself: 12345

Does memcpy use an independent pointer? Because the output definitely shows undefined behavior and restrict doesn't prevent access to overlapping memory with memcpy.

I'm assuming memcpy has a performance advantage since it copies data directly while memmove uses a buffer. But with modern computers, should I disregard this potentially better performance and always use memmove since it guarantees no overlap?

3

There are 3 best solutions below

0
On BEST ANSWER

The restrict keyword is a hint provided to the compiler to allow the generation of code, telling the compiler it should not be bothered with the possibility of pointer aliasing (two different named pointers accessing the same address).

In a function that takes restrict pointers, the compiler understands that writing to one will not impact the other. When copying from location A to location B, this means it can safely change this code :

  • Read from A[0] into register 1
  • Write to B[0] from register 1
  • Read from A[1] into register 1
  • Write to B[1] from register 1
  • Read from A[2] into register 1
  • Write to B[2] from register 1
  • Read from A[3] into register 1
  • Write to B[3] from register 1
  • ...

Into this sequence:

  • Read from A[0] into register 1
  • Read from A[1] into register 2
  • Read from A[2] into register 3
  • Read from A[3] into register 4
  • Write to B[0] from register 1
  • Write to B[1] from register 2
  • Write to B[2] from register 3
  • Write to B[3] from register 4
  • ...

These two sequences are identical only if A and B do not overlap and the compiler will not optimize into the second one unless you used restrict (or unless it can guess from the context that it is safe to do so).

If you end up providing aliased pointer to a function that does not expect them, the compiler may be able to warn you at compile time but there is no guarantee that it will. But you really should not expect an error at compile time - instead you should see it as a permission you grant to the compiler when it generates the assembly from your code.


Answering your question about performance - if you know for sure that there is no overlap in your pointers, use memcpy. If you don't, use memmove. memmove will generally check if there is an overlap and end up using memcpy if there isn't, but you pay for the check.

0
On

The memcpy function copies n bytes from memory area src to memory area dest directly.

By "directly" I suppose you mean that the function avoids copying first from source to a buffer and then from buffer to destination. Although this is likely to be true, the standard does not require it to be true.

memmove uses a buffer so there is no risk of overlapping memory.

No, memmove produces a result as if it copied first to a buffer and from there to the destination. It is not required to actually use a buffer in that way, so long as it yields the required result. Any given implementation might or might not do so.

Does memcpy use an independent pointer? Because the output definitely shows undefined behavior and restrict doesn't prevent access to overlapping memory with memcpy.

restrict doesn't prevent anything, ever. Compilers are not required to diagnose or even notice that you pass aliased pointers to restrict-qualified parameters. Indeed, that determination often cannot be made at compile time at all.

Indeed, when you call memcpy() with the arguments you do in your first call, you do provide two independent pointers to the same object as the source and destination arguments. As a result, the program's behavior is undefined.

Your program also exhibits undefined behavior as a result of computing itself - 14 (regardless of whether the resulting pointer is ever dereferenced). If itself were instead pointing at least 14 bytes into the interior of the allocated object, so that the pointer arithmetic was valid, then the arguments to the second memcpy() call would again be inconsistent with the requirements of the parameters' restrict qualification, so the program would exhibit UB for that reason, too.

I'm assuming memcpy has a performance advantage since it copies data directly while memmove uses a buffer. But with modern computers, should I disregard this potentially better performance and always use memmove since it guarantees no overlap?

That's a matter of opinion, and therefore off-topic here. I'll say only that questions of performance are best approached by first measuring, and then making decisions informed by the results of those measurements. Moreover, the performance characteristics of different implementations may vary.

1
On

restrict never stops undefined behaviour. In fact it introduces undefined behaviour in some cases. If restrict is removed from a piece of code that has no UB, then the code still has no UB; but the converse is not true.


Your code causes undefined behaviour at this line:

strcpy(itself, "Does restrict stop undefined behavior?");

due to overflowing the size of the allocated buffer. After that, all bets are off.

The restrict qualifier does not prevent buffer overflows.