cmemoryoverlap

How is memcpy.c supposed to react to memory overlap?


I'm trying to reproduce the behaviour of memcpy. However when I try it with overlapping memory tests, instead of a trace trap I receive a different result. For example with the following main function, the result would be OveOveOveOveO�.

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

int main() {

    char buffer[] = "Overlap Test";
    ft_memcpy(buffer + 3, buffer, strlen(buffer) + 1);
    printf("%s\n", buffer + 3);

    return 0;
}

I did put the restrict keywords in the function declaration, and I don't understand why the compiler wouldn't put it as a trace trap in this case. Of course chatgpt is useless finding an explanation for this as well. Would such a result be expected in case of memory overlap?

void    *ft_memcpy(void *restrict dst, const void *restrict src, size_t n)
{
    char    *ptr;
    char    *d;
    char    *s;

    d = dst;
    s = (char *)src;
    ptr = dst;
    if (src == NULL || n == 0)
        return (NULL);
    while (n > 0)
    {
        *d = *s;
        d++;
        s++;
        n--;
    }
    return (ptr);
}

Solution

  • I'm trying to reproduce the behaviour of memcpy.

    Bug: Wrong return value

    When n == 0, memcpy() returns dest. OP's code should perform likewise.

    // if (src == NULL || n == 0)
    //        return (NULL);
    if (src == NULL) // memcpy(..., NULL, ...) is not defined.  Do whatever you want.
      return NULL;  // or maybe `n = 0;`
    if (n == 0) 
      return dest;
    

    Bug: Insufficient space

    With char buffer[] = "Overlap Test";, ft_memcpy(buffer + 3, buffer, strlen(buffer) + 1); is a problem as buffer + 3 does not have enough room for strlen(buffer) + 1 characters. @chqrlie


    How is memcpy.c supposed to react to memory overlap?
    Would such a result be expected in case of memory overlap?

    Given the 2 restrict in the function signature, using overlapping buffers is undefined behavior (UB). There are no expected results.

    void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
    

    Even if OP's ft_memcpy() functioned just like OP's compiler's memcpy() today, it is not certain it will tomorrow or on some other machine. It is UB.

    I recommend to not test for equivalent functionality with overlapping buffers - since it is UB.
    Notice, that focus on this issue missed proper behavior when n == 0.


    Consider instead "How is memcpy.c memmove() supposed to react to memory overlap?"

    That is trickier.

    Copying takes place as if the n characters from the object pointed to by s2 are first copied into a temporary array of n characters that does not overlap the objects pointed to by s1 and s2, and then the n characters from the temporary array are copied into the object pointed to by s1.
    C23dr § 7.26.2.3 2

    OP's code needs changes to meet that. To do well is a challenge as the usual approach is to compare addresses. Depending on which is greater, copy from the beginning or end of the source buffer. Yet for user code, there is no defined way to compare arbitrary addresses for order. Various tricks abound that usually work.

    The below does not have UB, yet may not compile nor function exactly like memmove() on all machines.

    #include <stdlib.h>
    #include <stdint.h>
    
    void *ft_memmove(void *dst, const void *src, size_t n) {
      // Not needed as passing `NULL` for either pointer parameter is UB in memmove().
      if (dst == NULL || src == NULL) {
        n = 0;
      }
      // Weakness: uintptr_t is an optional type.
      uintptr_t d = (uintptr_t) dst;
      uintptr_t s = (uintptr_t) src;
      // Weakness: `d < s` is not a defined order compare of the pointers.
      if (d < s) {
        for (size_t i = 0; i < n; i++) {
          ((char*)dst)[i] = ((const char*)src)[i];
        }
      } else {
        while (n > 0) {
          n--;
          ((char*)dst)[n] = ((const char*)src)[n];
        }
      }
      return dst;
    }