Search code examples
cmemcpyrestrict-qualifier

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


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?


Solution

  • 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.