Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 33

Implementing `memcpy` and `memmove`

Recommended resources

Welcome to the first lesson of Module 5: Embedded Data Structures and Algorithms.

In the previous module, we concluded our deep dive into the hardware foundations of embedded systems, exploring everything from CPU pipelines and cache hierarchies to the secure boot process with ARM TrustZone. We established how the system works at a low level. Now, we shift our focus to the software that runs on top of that hardware, starting with fundamental algorithms that are critical for performance and correctness.

This lesson tackles a classic interview question that every C programmer in the semiconductor industry is expected to master. Your learning outcome is to: Implement memcpy and memmove from scratch, correctly handling overlapping memory regions in memmove.

Mastering this topic demonstrates a core competency in pointer manipulation, memory management, and an understanding of the C standard's rules on undefined behavior—all essential skills for low-level software development.

1. memcpy: The Fast but Fragile Workhorse

The memcpy function is a staple of the C standard library. Its purpose is simple: copy a block of n bytes from a source address to a destination address.

Its signature is:
void *memcpy(void *dest, const void *src, size_t n);

A naive but functional implementation could look like this:

void* my_memcpy(void* dest, const void* src, size_t n) {
    // Cast void pointers to char pointers for byte-wise access
    char* d = dest;
    const char* s = src;

    // Copy byte by byte from source to destination
    for (size_t i = 0; i < n; i++) {
        d[i] = s[i];
    }
    return dest;
}

This seems straightforward, but it hides a critical danger specified by the C standard. To understand the official rule and its practical implications, let's consult a definitive source.

memcpy() vs memmove() - Stack Overflow

The distinction between memcpy and memmove is governed by the C standard. This Stack Overflow discussion provides both the authoritative text from the standard and a clear code example showing where memcpy can fail.

Please read two specific answers in this thread: The answer by 'Ciro Santilli OurBigBook.com': Focus on the direct quotes from the C11 standard for memcpy and memmove. This is the ground truth. The answer by 'R srinivas reddy': This provides a simple, compelling code example that demonstrates the corruption that occurs when memcpy is used on overlapping buffers, while memmove works correctly.

As you read, the key takeaway is that for memcpy, if the source and destination memory regions overlap, the behavior is undefined. An implementation might work, or it might corrupt your data. You cannot rely on it.

Why does the overlap cause corruption?

Consider a simple forward copy where the destination address is inside the source block.

memcpy Overlapping Memory Issue
This diagram shows `memcpy` attempting to copy 8 bytes from `buf2[0]` to `buf2[6]`. When the copy operation reaches the 3rd byte (`buf2[2]`), its destination is `buf2[8]`. However, the original value of `buf2[8]` was supposed to be copied later. The simple forward copy has already overwritten its own source data, leading to corruption.

When dest > src and the regions overlap, a simple left-to-right (forward) copy will overwrite source bytes before they have been read, propagating incorrect data through the destination buffer.


2. memmove: The Safe and Robust Solution

The memmove function is the standards-guaranteed safe way to copy memory, especially when the regions might overlap.

Its signature is identical to memcpy:
void *memmove(void *dest, const void *src, size_t n);

The C standard describes its behavior "as if" it uses a temporary buffer. This ensures the original source data is read completely before any destination bytes are written. While that's a valid mental model, a temporary buffer is inefficient. The actual, high-performance implementation uses a clever trick: it chooses the copy direction based on the memory layout.

To see this in action, watch this short video.

memmove() Function | C Programming Tutorial

This video quickly contrasts memcpy and memmove and demonstrates a case where memmove handles an overlapping copy correctly.

Watch from 03:30 to 06:52. This segment directly compares the two functions and provides a concrete example of how memmove succeeds where memcpy's behavior would be undefined.


3. Implementing memmove from Scratch

The core logic of an efficient memmove implementation is this:

  1. Check for overlap and relative positions.
  2. If the destination buffer is at a lower memory address than the source (dest < src), or if there is no overlap, a forward copy (from start to end) is safe.
  3. If the destination buffer is at a higher memory address and overlaps with the source (dest > src), a backward copy (from end to start) is necessary to avoid data corruption.
Memory Overlap: Destination Lower Than Source
In this scenario, `dest < src`. A forward copy is safe because writing to the destination does not overwrite source data that has yet to be read.

Let's now build our own memmove implementation. This is precisely what you might be asked to do in a technical interview.

First, let's examine two professional implementations to understand the pattern.

memcpy() vs memmove() - Stack Overflow

Now that we understand the theory, let's look at two different from-scratch implementations of memmove. Pay close attention to how they decide whether to copy forwards or backwards.

First, review the memmoveCustom implementation in the answer by 'singingsingh'. Then, look at the GCC implementation provided in the answer by 'pbies' (at the very end of his answer). Notice they use slightly different logic (ps < pd vs d < s) but achieve the same goal. We will base our implementation on the GCC version's logic.

The Code

Drawing from the logic of the GCC implementation, here is a clean, correct version of memmove that you can study and reproduce.




#include <stdio.h>
#include <string.h> // For the real memmove and for size_t

void* my_memmove(void* dest, const void* src, size_t n) {
    // Cast to byte-pointers for manipulation
    unsigned char* d = dest;
    const unsigned char* s = src;

    // Handle trivial cases
    if (d == s || n == 0) {
        return dest;
    }

    // If destination is at a lower address or there's no overlap,
    // a forward copy is safe.
    if (d < s) {
        while (n--) {
            *d++ = *s++;
        }
    } 
    // Otherwise, destination is at a higher address and might overlap.
    // A backward copy is required.
    else {
        // Set pointers to the end of the blocks
        d += n;
        s += n;
        while (n--) {
            *--d = *--s;
        }
    }

    return dest;
}

// --- Test Harness ---
void print_array(const char* label, const char* arr, size_t size) {
    printf("%s: \"", label);
    for (size_t i = 0; i < size; ++i) {
        // Print null terminators as '\0' for clarity
        if (arr[i] == '\0') {
            printf("\\0");
        } else {
            printf("%c", arr[i]);
        }
    }
    printf("\"\n");
}

int main() {
    char buffer[20] = "abcdefghij"; // Initial string, 10 chars + null terminator

    printf("--- Test Case: Overlap, dest > src ---\n");
    // Expected to fail with a naive memcpy, succeed with memmove
    // Copy 'abcde' from buffer[0] to buffer[2]
    // Expected result: "ababcdehij"
    print_array("Before", buffer, 15);
    my_memmove(&buffer[2], &buffer[0], 5);
    print_array("After ", buffer, 15);
    
    printf("\n");

    printf("--- Test Case: Overlap, dest < src ---\n");
    // Forward copy is safe here
    // Copy 'cde' from buffer[2] to buffer[0]
    strcpy(buffer, "abcdefghij"); // Reset buffer
    print_array("Before", buffer, 15);
    my_memmove(&buffer[0], &buffer[2], 5);
    print_array("After ", buffer, 15);

    printf("\n");
    
    printf("--- Test Case: No Overlap ---\n");
    char dest_buf[10] = {0};
    strcpy(buffer, "abcdefghij"); // Reset buffer
    print_array("Before", buffer, 15);
    my_memmove(dest_buf, buffer, 5);
    print_array("Dest  ", dest_buf, 10);
    
    return 0;
}

Experiment: I encourage you to compile and run this code. Then, create a my_memcpy using the simple forward-copy loop from the beginning of the lesson and substitute it in the dest > src test case. You will see the data corruption firsthand.


Conclusion

In this lesson, you have tackled a fundamental piece of C programming that is both practical and a frequent subject of technical interviews.

Key Takeaways:

  • memcpy is for non-overlapping memory regions. Using it on overlapping regions results in undefined behavior. It is potentially faster because its implementation can be more aggressive with optimizations, assuming no overlap.
  • memmove is for any memory region, overlapping or not. It guarantees correctness by checking the relative positions of the source and destination pointers and choosing the copy direction accordingly.
  • A correct memmove implementation does not need a temporary buffer. It uses a forward copy (dest < src) or a backward copy (dest > src) to avoid overwriting its own source data.

Preview of the Next Lesson:

Now that we've solidified our understanding of safe memory manipulation, we will move on to another critical data structure in embedded systems: the circular buffer (or ring buffer). In the next lesson, we will learn how to implement a lock-free circular buffer for a single-producer/single-consumer scenario, a common pattern for passing data between ISRs and main-line code or between two different tasks.