I wrote this code as an answer to a question. But I'd like you to have a look at it. This post is basically a copy of [the answer](https://stackoverflow.com/a/68237099/6699433) I posted

The code does no error checking. It assumes that the output buffer exists and is big enough and that src, orig and new are valid strings. The headers needed:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stddef.h>
    #include <stdint.h>

The main replace function
    
    // Replace the first occurrence of orig in src with new and write result to dest
    // Return pointer to first character after the end of first match in src and
    // NULL if no match
    const char *replace(char *dest, const char *src,
    		    const char *orig, const char *new) {
        char *ptr = strstr(src, orig); // First match
    
        // If no match, we want dest to contain a copy of src
        if(!ptr) {
    	    strcpy(dest, src);
    	    return NULL;
        }
    
        const ptrdiff_t offset = ptr - src; // Calculate offset
        const int origlen = strlen(orig);
        
        strncpy(dest, src, offset); // Copy everything before match
        strcpy(&dest[offset], new); // Copy replacement
        strcpy(&dest[offset + strlen(new)], &src[offset + origlen]); // Copy rest
    
        return src + offset + origlen;
    }



Then we just add a function to handle N occurrences

    // Replace maximum n occurrences. Stops when no more matches.
    // Returns number of replacements
    size_t replaceN(char *dest, const char *src, const char *orig, 
                    const char *new, size_t n) {
        size_t ret = 0;
    
        // Maybe an unnecessary optimization to avoid multiple calls in 
        // loop, but it also adds clarity
        const int newlen = strlen(new);
        const int origlen = strlen(orig);
        
        do {
    	    const char *ptr = replace(dest, src, orig, new); // Replace
    
    	    if(!ptr) return ret; // Quit if no more matches
    
    	    // Length of the part of src before first match
    	    const ptrdiff_t offset = ptr - src;
    	
    	    src = ptr; // Move src past what we have already copied.
    	
    	    ret++;
    
    	    dest += offset - origlen + newlen; // Advance pointer to dest to the end
    
        } while(n > ret);
    
        return ret;
    }


And a simple wrapper for all occurrences. Note that it's safe to use `SIZE_MAX` here, because there will never be more occurrences than that.
    
    // Replace all. Returns the number of replacements, because why not?
    size_t replaceAll(char *dest, const char *src, const char *orig, const char *new) {
        return replaceN(dest, src, orig, new, SIZE_MAX); 
    }



It's easy to write a wrapper for the allocation. We borrow some code from https://stackoverflow.com/q/9052490/6699433

    size_t countOccurrences(const char *str, const char *substr) {
        size_t count = 0;
    
        while((str = strstr(str, substr)) != NULL) {
           count++;
           str++; // We're standing at the match, so we need to advance
        }
    
        return count;
    }


Then some code to calculate size and allocate a buffer

    // Allocate a buffer big enough to hold src with n replacements 
    // of orig to new
    char *allocateBuffer(const char *src, const char *orig, 
                         const char *new, size_t n) {
        return malloc(strlen(src) + 
                      n * (strlen(new) - strlen(orig)) +
                      1 // Remember the zero terminator
                     );
    }


And the two final functions

    // Allocates a buffer and replaces max n occurrences of orig with new and
    // writes it to the allocated buffer.
    // Returns the buffer and NULL if allocation failed
    char *replaceNAndAllocate(const char *src, const char *orig, 
                              const char *new, size_t n) {
        const size_t count = countOccurrences(src, orig);

        n = n < count ? n : count; // Min of n and count

        char *buf = allocateBuffer(src, orig, new, n);

        if(!buf) return NULL;
    
        replaceN(buf, src, orig, new, n);
    
        return buf;
    }

    // Allocates a buffer and replaces all occurrences of orig with new and
    // writes it to the allocated buffer.
    // Returns the buffer and NULL if allocation failed
    char *replaceAllAndAllocate(const char *src, const char *orig, const char *new) {
        return replaceNAndAllocate(src, orig, new, SIZE_MAX);
    }



And finally, a simple `main` with a test with multiple occurrences and with original string being a substring of replacement string:

    int main(void) {
        char src[] = "!!!asdf!!!asdf!!!asdf!!!asdf!!!";
        char orig[] = "asdf";
        char new[] = "asdfaaaaaaaaasdf";
        
        puts(replaceAllAndAllocate(src, orig, new));
    }

No warnings with `-Wall -Wextra -pedantic` and the output is:

    $ ./a.out 
    !!!asdfaaaaaaaaasdf!!!asdfaaaaaaaaasdf!!!asdfaaaaaaaaasdf!!!asdfaaaaaaaaasdf!!!