Without any additional information about what your code is doing, I would recommend designing all your interfaces like this:
size_t foobar(char *dest, size_t buf_size, /* operands here */)
with semantics like snprintf
:
dest
points to a buffer of size at least buf_size
.
- If
buf_size
is zero, null/invalid pointers are acceptable for dest
and nothing will be written.
- If
buf_size
is non-zero, dest
is always null-terminated.
- Each function
foobar
returns the length of the full non-truncated output; the output has been truncated if buf_size
is less than or equal to the return value.
This way, when the caller can easily know the destination buffer size that's required, a sufficiently large buffer can be obtained in advance. If the caller cannot easily know, it can call the function once with either a zero argument for buf_size
, or with a buffer that's "probably big enough" and only retry if you ran out of space.
You can also make a wrapped version of such calls analogous to the GNU asprintf
function, but if you want your code to be as flexible as possible I would avoid doing any allocation in the actual string functions. Handling the possibility of failure is always easier at the caller level, and many callers can ensure that failure is never a possibility by using a local buffer or a buffer that was obtained much earlier in the program so that the success or failure of a larger operation is atomic (which greatly simplifies error handling).