Split function using C and dynamic memory allocation
Design: Code should explain cases

Double space: &quot;abc  xyz&quot; --&gt; Is that to be 2 or 3 tokens?
Leading space: &quot; abc xyz&quot; --&gt; Is that to be 2 or 3 tokens?
Trailing space: &quot;abc xyz &quot; --&gt; Is that to be 2 or 3 tokens?

Design : Generalization
split() only considers ' '.  Maybe all white-spaces, '\t', '\n', ...?
Or pass into the function  a list of separators: split(char *str, const char *separators)
Design: const
As data referenced by str does not change, consider char **split(const char *str) for greater applicability and self documentation.
Bug: Insufficient memory
// word_array[a] = malloc(word_len * sizeof(char));
word_array[a] = malloc((word_len + 1u) * sizeof(char));
...
word_array[a][word_len] = '\0';

Bug: Writing outside allocation
word_array = malloc((separator_count + 2) * sizeof(char *));  
...
word_array[separator_count + 2] = NULL; //OOPS!

// I suspect OP wanted  
word_array[separator_count + 1] = NULL;

Allocate to the size of the reference object, not the type
// word_array = malloc((separator_count + 2) * sizeof(char *)); 
word_array = malloc(sizeof *word_array * (separator_count + 2u)); 


// word_array[a] = malloc(word_len * sizeof(char));
word_array[a] = malloc(sizeof (word_array[a][0] * word_len);

It is easier to code right, review and maintain.
Leading with the widest types in the multiplication prevents overflow - though that is not so important in this case.
Robust code checks allocation success
ptr = malloc(sizeof *ptr * n);
if (ptr == NULL &amp;&amp; n &gt; 0) {
  HandleOutOfMemory(); // Some user code
}

Clean-up allocations
main() called split(s) which allocates, but failed to call a matching free().  Not much concern with main() as code ends anyways, but good practice.
Minor: int word_len insufficient for large strings
// int word_len 
size_t word_len