String separator, similar to strtok
I'm also leaking some memory according to valgrind, but that is only a minor concern for my use case.


Hmm, I don't really agree. Leaking memory is harmless only if you know the cause (*), and simply choose to not spend time in fixing it. In your case, the cause (for one part, see below) is the print loop at the end of the main:

while(*strings){
    printf("%s\n", *(strings));
    free(*(strings++));  // incrementing strings here
}

free(strings); // string no longer points to the initially allocated bloc: UB


Whether it matters is your problem, but you should not reproduce this code. BTW, it caused a crash on my VS2017...

Now for the function itself:


char** strings = malloc(0); Nothing really bad here because it will be reallocated later, but as it is more of a trick, it really deserves a comment explaining the reason for that line
Your naming convention for local variables (int i = 0, j = 0, k = 0, l = 0, stringCount = 0;) does not help to understand their meaning. After analysis I would prefer:

i -&gt; buflen
j -&gt; seplen
k -&gt; buf_ix  // or whatever you want saying it is intended to be an index of buf
l -&gt; sep_ix

the strings in strings are not null-terminated. This is really bad because there is no way to find the used length: you really should add that terminating null - anyway, you have reserved memory for it...
An additional memory leak when setting last element of strings to NULL:

/* Append NULL to array of strings */
strings = realloc(strings, (stringCount+1) * sizeof (char *));
strings[stringCount] = malloc(sizeof NULL);    // hmm... a dynamic allocation...
strings[stringCount] = NULL;                   // immediately and definitively lost !


this code should become:

/* Append NULL to array of strings */
strings = realloc(strings, (stringCount+1) * sizeof (char *));
strings[stringCount] = NULL;

you should test malloc and realloc return values. Allocation error are uncommon nowadays, but if this code was to run under stressed condition very bad things would occur, because you would be using a null pointer which is Undefined Behaviour (other details on @TobySpeight's answer).




(*) Even if you had identified that the code in main caused a memory leak, it could have masked the other memory leak inside the function the deserved to be fixed. That's the reason why it is bad to leave a memory leak in a program, and even in a test only one!