Use the first 7 bits of the byte as significant digits, the last bit as a boolean (the next byte is String payload or another byte for string length).
For instance, if you copy a string you also have to update the end pointer instead of just copying the size attribute in bulk. And you get the same disadvantages of non-portable strings, different representations depending on the architecture/endianess etc...
I completely agree with the OP, there's no perfect solution. If addr + len was truly superior I'm sure we'd see
struct string { long len; char s[]; };
or for your version struct string { char *endptr; char s[]; };
everywhere. And the C standard library would have evolved along with it.Out of the top of my head the only thing that makes '\0' terminated strings special in C is that it's the way string literals are represented. It would be trivial to recode all of string.h using addr + len instead of nul terminated.
Except that it has the important property that the (effective) length descriptor, being a pointer, would necessarily "grow" over time (across generations of machines, e.g. 16 bit -> 32 bit, etc.) and would thus never impose any artificial restrictions on string length.
exactly, anywhere you would have if(str[i] == NULL) you replace with if (str.s + i == str.end)
Like what Churchill said about democracy: NUL-terminated is a terrible solution, but it beats whatever's in second place.
So, if we went down the pass, what will we have? All the fun of having "legacy" APIs that seem to work but internally only accept strings up to 64kb length and mysteriously chop off excess bytes when you least expect it. It's Y2K problem all over again.
And just when you finally think you're over with it, memory is cheaper again, size_t is 64bit, and someone invariably wants to store a binary blob >4G as string. Fun time again.
Have we forgotten how much trouble we went through in the 90s to handle memory in x86 "640k is enough for everybody" architecture?