In a response elsewhere, I found the following snippet:
In general it is nicer in C to have the caller allocate memory, not the callee - hence why str
It all comes down to establishing ownership of the memory.
When projects get very large, it can become difficult to figure out where all the memory is going.
In C++, we often get around this by using a factory such as the foo_create() example. That factory knows how to setup foo objects, and can easily track how much memory it is allocating and how much it is freeing.
While something similar can be done in C, often we simply make sure that each layer of your program cleans up the memory it uses. Thus, a reviewer can glance at the code to make sure that each malloc has a matching free. When allocations are too deeply nested, it can quickly become unclear where a memory leak occurs.
By the way, I tend to lean toward having an initializer that is separate from allocation for the sake of returning an error value from the initializer. If you simply call foo_create(), and get a null pointer back, it is not clear if the creation failed due to lack of memory, or due to some other reason. Getting into the habit of having return values on init functions can save you a lot of debugging time.