Working with Raw Memory in C: memcpy, memmove, memset, and memcmp

The <string.h> header provides a suite of functions designed for direct memory manipulation. Unlike string-specific routines, these utilities operate on raw byte sequences, making them type-agnostic and highly versatile for low-level data handling.

memcpy: Block Memory Copy

Prototype:

void *memcpy(void *dest, const void *src, size_t count);

This routine copies exactly count bytes from the source address to the destination address. It returns the original destination pointer. The C standard explicitly states that behavior is undefined if the source and destination regions overlap. While some compiler implementations may handle overlap gracefully, portable code must assume it does not.

Because the parameters use void *, the function operates independently of data types. Internally, it treats memory as a sequence of bytes.

Custom Implementation:

#include <assert.h>
#include <stddef.h>

void *custom_memcpy(void *target, const void *origin, size_t bytes) {
    assert(target != NULL && origin != NULL);
    unsigned char *t = (unsigned char *)target;
    const unsigned char *o = (const unsigned char *)origin;

    while (bytes > 0) {
        *t++ = *o++;
        bytes--;
    }
    return target;
}

Pointer arithmetic on void * is illegal in standard C, so casting to unsigned char * ensures single-byte increments. The loop decrements the byte counter until the entire block is transferred. Assertions prevant null pointer dereferencing, improving runtime safety.

memmove: Safe Copy for Overlapping Regions

Prototype:

void *memmove(void *dest, const void *src, size_t count);

memmove shares the same signature as memcpy but guarantees correct behavior even when memory blocks overlap. It effectively replaces memcpy in scenarios where region boundaries are uncertain.

Custom Implementation:

void *custom_memmove(void *target, const void *origin, size_t bytes) {
    assert(target != NULL && origin != NULL);
    unsigned char *t = (unsigned char *)target;
    const unsigned char *o = (const unsigned char *)origin;

    if (t > o && t < o + bytes) {
        // Overlap detected: target starts inside source block
        // Copy backwards to prevent data corruption
        t += bytes;
        o += bytes;
        while (bytes > 0) {
            *--t = *--o;
            bytes--;
        }
    } else {
        // No overlap or target precedes source: copy forwards
        while (bytes > 0) {
            *t++ = *o++;
            bytes--;
        }
    }
    return target;
}

When the destination addresss lies within the source range, forward copying would overwrite unread source bytes. The solution checks pointer relationships: if target > origin and overlap exists, the routine shifts both pointers to the end of the block and copies backward. Otherwise, standard forward iteration is safe.

memset: Byte-Wise Memory Initialization

Prototype:

void *memset(void *ptr, int value, size_t count);

This function fills the first count bytes of the memory block pointed to by ptr with the constant byte value. The integer argument is implicitly converted to an unsigned char.

Usage Considerations:

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

int main(void) {
    int numbers[5] = {10, 20, 30, 40, 50};
    memset(numbers, 0x09, 5); // Sets first 5 bytes to 0x09

    for (int i = 0; i < 5; i++) {
        printf("%d ", numbers[i]);
    }
    return 0;
}

Since memset operates per-byte, applying it to multi-byte types like int yields results based on hexadecimal byte patterns. Setting 5 bytes to 9 modifies the memory layout directly, often producing large or unexpected integer values due to endianness and byte alignment. For character arrays, the behavior is more intuitive:

char text[10] = "hello";
memset(text, 'X', 3); // Results in "XXXlo"

The function does not append null terminators; it strictly overwrites the specified byte range.

memcmp: Raw Memory Comparison

Prototype:

int memcmp(const void *ptr1, const void *ptr2, size_t count);

Compares the first count bytes of two memory blocks. Returns a negative value if ptr1 is less than ptr2, zero if they match, and a positive value if ptr1 is greater. Comparison is performed byte-by-byte using unsigned char values.

Example:

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

int main(void) {
    int set_a[] = {1, 2, 5};
    int set_b[] = {1, 2, 6};

    // Compare first 8 bytes (two 4-byte integers)
    int res1 = memcmp(set_a, set_b, 8);
    printf("First 8 bytes: %d\n", res1); // Outputs 0

    // Compare first 9 bytes
    int res2 = memcmp(set_a, set_b, 9);
    printf("First 9 bytes: %d\n", res2); // Outputs negative value
    return 0;
}

On a little-endian system, the integer 5 is stored as 05 00 00 00 and 6 as 06 00 00 00. Comparing 8 bytes covers the first two integers exactly, yielding equality. Extending the comparison to 9 bytes includes the first byte of the third integer, where 0x05 is less than 0x06, resulting in a negative return value. This demonstrates how memcmp evaluates raw memory layouts rather than logical data types.

Tags: c programming Memory Management standard library pointers Systems Programming

Posted on Wed, 13 May 2026 09:50:51 +0000 by Formula