NUL terminated strings are a great representation. We can take a suffix of a non-empty NUL-terminated string just by incrementing the pointer, which is great for recursion. A pointer to a NUL is itself a valid string.
E.g. tail recursive strchr:
const char *strchr_rec(const char *str, int ch)
{
if (*str == 0)
return NULL;
if (*str == ch)
return str;
else return strchr_rec(str + 1, ch);
}
strrchr (rightmost match):
const char *strrchr_tail(const char *str,
int ch,
const char *seen)
{
if (*str == 0)
return seen;
if (*str == ch)
return strchr_tail(str + 1, str, ch);
return strchr_tail(str + 1, seen, ch);
}
const char *strchr_rec(const char *str, int ch)
{
return strchr_tail(str, ch, 0);
}
That kind of thing makes me feel that null terminated strings are the shiznit compared to some clunky header + data or whatever.
The only problem is that NUL-terminated strings are so darn easy to program with, everyone who comes into contact with them wants their share of the action, instead of using someone else's well-tested code.
As we speak, someone is likely writing yet another loop that builds up a NUL-terminated string directly, and will forget to write the final NUL in some case, or go past the end of the buffer in another.
Want to break a string into tokens on delimiters? Just overwrite the delimiters with null bytes and you're done. (Oops, the caller didn't expect their string mutated. Worse, it was a literal ...).
If you make programmers maintain a length field followed by data, or some such thing, they will quickly go off scampering for library functions to everything.
What we should never do is compare unencapsulated NUL-terminated string manipulation with the manipulation of another representation using a robust, debugged library.
Compare open-coded to open-coded, wrapped to wrapped.