Splitting C string without modifying input I wrote this after using strtok_r and needing something similar that can copy the input string and handle multiple characters in the delimiter. Its functionally similar to Python's str.split(), as it keeps empty spaces without trimming.

tokens.h

#ifndef TOKENS_H
#define TOKENS_H

#include &lt;string.h&gt;

struct Tokens {
    char **array;
    size_t count;
};

extern struct Tokens *string_split(const char *input,
                                   const char *delim,
                                   const int count);

extern void tokens_free(struct Tokens *tokens);

#endif


tokens.c

#include "tokens.h"

#include &lt;stdbool.h&gt;
#include &lt;stdlib.h&gt;

static bool tokens_add(struct Tokens *tokens, const char *token){
    tokens-&gt;array = realloc(tokens-&gt;array,
                            ++tokens-&gt;count * sizeof(char *));

    if (!tokens-&gt;array){
        tokens-&gt;count--;

        return false;
    }

    char *dupstr = strdup(token);

    if (!dupstr){
        tokens-&gt;count--;

        return false;
    }

    tokens-&gt;array[tokens-&gt;count - 1] = dupstr;

    return true;
}

struct Tokens *string_split(const char *input,
                            const char *delim,
                            const int count){

    struct Tokens *tokens = malloc(sizeof(struct Tokens));

    if (!tokens){
        return NULL;
    }

    tokens-&gt;count = 0;
    tokens-&gt;array = NULL;

    const size_t inputlen = strlen(input);
    const size_t delimlen = strlen(delim);

    if (count == 0 || delimlen == 0){
        tokens_add(tokens, input);

        return tokens;
    }

    int delimcount = 0;
    size_t inputpos = 0;
    const char *start = NULL;

    while ((start = strstr(&amp;input[inputpos], delim))){
        const int delimpos = (start - &amp;input[inputpos]);
        char token[delimpos + 1];

        memcpy(token, &amp;input[inputpos], delimpos);
        token[delimpos] = '\0';

        if (!tokens_add(tokens, token)){
            return NULL;
        }

        inputpos += (delimpos + delimlen);

        if (delimcount++ == count){
            break;
        }
    }

    if (inputpos &lt;= inputlen){
        const int charcount = (inputlen - inputpos);
        char token[charcount + 1];

        memcpy(token, &amp;input[inputpos], charcount);
        token[charcount] = '\0';

        if (!tokens_add(tokens, token)){
            return NULL;
        }
    }

    return tokens;
}

void tokens_free(struct Tokens *tokens){
    if (!tokens){
        return;
    }

    for (size_t index = 0; index &lt; tokens-&gt;count; index++){
        free(tokens-&gt;array[index]);
    }

    free(tokens);
}


test.c

#include &lt;stdio.h&gt;

#include "tokens.h"

int main(void){
    // &lt;0 = split indefinitely
    struct Tokens *tokens = string_split("Hello;world;!;", ";", -1);

    if (tokens){
        for (size_t index = 0; index &lt; tokens-&gt;count; index++){
            printf("%s\n", tokens-&gt;array[index]);
        }

        tokens_free(tokens);
    }
}
token_add()


do not change the struct members before you know that your call will succeed, otherwise you'll end up with inconsistent state (e.g. you're overwriting tokens-&gt;array if realloc fails). That means keep the increment of count and the assignment of array until the end of the function.
Note that strdup is not a C standard function. 


split_string()


you're creating a temporary copy in the local variables token. If you'd pass the pointer and the length to token_add and you could safe the program a string copy.
there is no need to have delimpos in string_split being const. Same goes for the parameter count and charcount. As a rule of thumb, dont use const for integer types, booleans and floating points.
Note: your comment in test.c states that values less than 0 mean unlimited matching, but your code doesn't contribute to that statement. If you have enough matches you'll eventually increment delimcount to match -1.
Separate the increment of delimcount from the comparison with count. Some will ask himself if this is correct and right now, I couldn't tell ;-)
Why don't you use tokens-&gt;count?
If a call to tokens_add failes, you're returning NULL from split_string without freeing the Tokens struct allocated before, leaking its memory.


misc


Provide more test cases. One test case for handling unlimited matching, one for restricted matching, one for empty delimiters. Best is to write down what your API supports and then add a test for every special case.


For instance, if I test the following:

    struct Tokens *tokens = string_split("Hello;world;!;", ";", 2);

    assert(tokens-&gt;count == 2);
    assert(strcmp("Hello", tokens-&gt;array[0]) == 0);
    assert(strcmp("world!", tokens-&gt;array[1]) == 0);


I receive: test: test.c:22: test_2: Assertion 'tokens-&gt;count == 2' failed.


you don't need the include in tokens.h, it's sufficient if you add it to tokens.c