2
\$\begingroup\$

How does the following look for doing a string replace in C? Is this approach more common than allocating memory within the function and returning a new string pointer, or is the bring-your-own-buffer more common?

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


// return size of string, -1 if error such as buffer too small
// no memory is allocated here, buffer needs to be passed in
// Example: str_replace("Replace me", "me", "ME", buffer, 64);
// Returns: 10, buffer is now "Replace ME".
ssize_t 
str_replace (
        const char* input_str, 
        const char* from, 
        const char* to, 
        char* output_buffer, 
        size_t max_size
) {
    size_t input_len = strlen(input_str);
    size_t from_size = strlen(from);
    size_t to_size = strlen(to);

    // get the output string length;
    size_t output_len = input_len;
    char* str_iterator = (char*) input_str;
    while ((str_iterator=strstr(str_iterator, from))) {
        output_len += (to_size - from_size);
        str_iterator += from_size;
    }
    if (output_len > max_size) {
        fprintf(stderr, "Buffer too small for output string.\n");
        return -1;
    }

    // reset str_iterator, make sure output_buffer is clear
    str_iterator = (char*) input_str;
    output_buffer[0] = '\0';

    // copy to output buffer
    // 1. Find the first match of `from` in `input_str` (advancing pointer on input_buffer along way)
    // 2. Copy the input_str up until the first match to output_buffer
    // 3. Copy `from` into output_buffer (advance string)
    // 4. Advance input_buffer pointer by size of `from`
    // 5. Repeat 2-5 until no more matches
    // 6. Copy remainder of input_str to output_buffer
    for (char* match; (match=strstr(str_iterator, from)); str_iterator+= (match-str_iterator) + from_size) {
        strncpy(output_buffer, str_iterator, match-str_iterator);
        output_buffer += (match-str_iterator);
        strncpy(output_buffer, to, to_size);
        output_buffer += to_size;
    }
    // copy remainder
    if (strlen(str_iterator))
        strcpy(output_buffer, str_iterator);

    return output_len;
}
int main(void)
{
    char buffer[50];
    ssize_t out_size = str_replace("Replace me", "me", "ME", buffer, 50);
    if (out_size == -1)
        printf("Nope!\n");
    else
        printf("Response size: %zu.\nResponse string: %s\n", out_size, buffer);
}

$ gcc logmain.c -o log; ./log
Response size: 10.
Response string: Replace ME

And another alternative:

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

// same as above but now with malloc
char* str_replace2(const char* input, const char* from, const char* to)
{
    size_t input_len = strlen(input);
    size_t from_size = strlen(from);
    size_t to_size = strlen(to);
    char* str_iterator = (char*) input;

    // get the output string length;
    size_t output_len = input_len;
    while ((str_iterator=strstr(str_iterator, from))) {
        output_len += (to_size - from_size);
        str_iterator += from_size;
    }
    char *str_out = malloc(output_len);
    char * const str_out_start = str_out; // const for emphasis it doesn't change
    if (!str_out) {
        fprintf(stderr, "Cannot malloc str of size %zu", output_len);
        return NULL;
    }

    // write to output str
    str_iterator = (char*) input;
    for (char* match; (match=strstr(str_iterator, from)); str_iterator+= (match-str_iterator) + from_size) {
        strncpy(str_out, str_iterator, match-str_iterator);
        str_out += (match-str_iterator);
        strncpy(str_out, to, to_size);
        str_out += to_size;
    }
    // copy remainder
    if (strlen(str_iterator))
        strcpy(str_out, str_iterator);

    return str_out_start;
}

Additionally, where are comments usually put in C? Above the function? In the function?

\$\endgroup\$
1
  • \$\begingroup\$ What about not passing a string? or something not properly terminated? \$\endgroup\$
    – Mr R
    Commented Mar 19, 2021 at 4:09

1 Answer 1

2
\$\begingroup\$

Few things to consider.

  1. Why are you writing your own implementation of something that already exists, long history of reliable use with well defined behaviour (fun/assignment - sure, generally - waste of effort likely to lead to problems)?

  2. What if input, from, or to are null, or not valid (not \0 terminated), OR overlap in any way (will it work)?

  3. from and to, might imply to some people replace from here, to there. Names are important - look for things that are self-evident. Perhaps matchToReplace and replaceMatchWith - a bit wordy, but hopefully less ambiguous.

  4. What happens if from is empty - what is the sensible result (document it)?

  5. Don't you need to malloc one more than the desired length (for the terminating \0) [didn't read the algorithm carefully enough sorry].

  6. Make sure you have test cases checking all the above, but also confirming that when you copy 0 length things, or 1 - that everything is copied properly.

  7. Documenting - what tool are you using to publish it? Certainly doesn't hurt to put a comment before - but remember what people are looking at is the headers - for the declaration, not the implementation (generally at least).

\$\endgroup\$
2
  • 1
    \$\begingroup\$ "Why are you writing your own implementation..." I'm a beginner in C and just practicing doing things. Reading others code and implementing things, etc. \$\endgroup\$
    – carl.hiass
    Commented Mar 19, 2021 at 4:25
  • \$\begingroup\$ maybe this is a bit too much to ask, but would you be able to suggest solutions for 1-7. It can even be a sentence without any code, just some ideas for when I implement all your suggestions. For example, what are choices for doing tests or documenting ? \$\endgroup\$
    – carl.hiass
    Commented Mar 19, 2021 at 18:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.