I have seen it claimed that null-terminated strings were encouraged by the instruction sets of the time -- that some instruction sets make null-terminated sequences easier to handle than length-prefixed ones. The article's error-message-printing code snippet is a good example. Does anyone think there is any truth to this?
Null terminated is going to be nice in most instruction sets, you don't need to keep track of a count, so you save a register, and you have one less thing to increment (or decrement). Loop condition is basically free too, loading the next byte into a register is going to set the status register, so you don't need a compare, you can just branch if the zero flag is set.
As long as you are handling good data, it's clearly more efficient.
Aside from the Zero flag, there was also a Negative/Signed flag. Setting the high-bit in the last character (or clearing it if the rest had the high bit set) was also a way to terminate strings and save a byte over null-terminated strings.
People who go ape-shit over 0-terminated strings would probably be even more upset if we still did that.
The ASCIZ directive on the PDP-11 assembler took a string literal and blitted it to memory with a trailing NULL, so there was a convenience factor there. BCPL and B used null-terminated strings, so it made sense for C to inherit the practice.
Pascal strings were significantly size-limited due to memory constraints (an implementation detail), so there was a reason to prefer null termination, but in essence we're still slaves to an obsolete instruction set.
Yes, the architecture on which you're most likely reading this (x86 or x64) also has semi-dedicated string handling instructions, akin to higher level string functions.
For example, a string copy 'instruction' that copies a 0 terminated string from one location to another looks like this:
lea esi, source_string
lea edi, dest_string
repnz movsb ; copies esi -> edi until *esi = 0
Whoops, looks like I've indeed mixed up my assembler.
While the rep/repe/repnz can be used in conjunction with movs(b/w) it will not terminate on the z flag in the eflags register (so it won't stop if *esi == 0) but rather when ecx = 0.
So ecx needs to contain the size of the string you want to move, and rep movsb does not use the zero flag.
Maybe that's not the whole picture, but I think a big reason for null terminated strings is that C has pointers. A char pointer makes it practical to move through strings and a NULL terminator is easier to handle than a size test. In the end, sized strings make C code more complicated, unlike other languages such as Pascal.
True, I should have said pointers and pointer arithmetic. In Pascal you cannot do these things because there is no pointer arithmetic to move along strings and arrays.
I have made no comment about the quality of Pascal vs. C. In fact I like Pascal a lot, my comment is just on the way to write idiomatic code in each language. In C it is much easier and common to traverse data structures. Pascal may do it in some implementations, but its not how programmers used to organize their code. This just means that for C programmers it is frequently easier to work with NULL terminated strings.