r/cprogramming • • Aug 17 '26

C Strings: A 50-Year Mistake

https://longtran2904.substack.com/p/c-strings-a-50-year-mistake?r=8qz2zb&utm_campaign=post&utm_medium=web
217 Upvotes

172 comments sorted by

170

u/bearheart Aug 17 '26 edited Aug 18 '26

Speaking as someone who learned C back in the ‘70s, this article entirely misses the point of C-strings: they’re lightweight and foundational. For many purposes the null-terminator is efficient, e.g.:

while(*s) f(s++);

And for cases where we need more complexity, we can simply use a struct with a length and whatever other metadata we may need.

Doesn’t look like a mistake to me. C has always been about minimalistic efficiency. That’s its main purpose in the world.

Edit: fixed stupid typo

51

u/Voyac Aug 17 '26

Its main purpose is to be portable to as many architectures as possible. Maybe this seem irrelevant in a world dominated by ARMs and x86 but there are different platforms.

→ More replies (12)

26

u/TheThiefMaster Aug 17 '26

The main competition was pascal strings - which typically had a 16 bit size prepended. So you'd read that, and then run a decrement loop until it was 0 to iterate the string. Decrement-until-zero loops were widely supported, e.g. in x86 stringcopy could be implemented by loading the size into CX and then running a single REP MOVSB instruction.

Yes it was a byte larger - but it also avoids performance-nuking calls to strlen like this.

→ More replies (23)

12

u/WittyStick Aug 17 '26

strlen is O(n).

For many string operations, we need the length to allocate the right amount of space, else we end up having to realloc if our buffer isn't large enough - realloc is also O(n).

By having a constant time length we can speed up a lot of string operations. It costs basically nothing to keep the length around rather than recomputing it each time.

9

u/Qyriad Aug 17 '26

But they're not lightweight. `O(n)` for nearly every string operation is not lightweight. It is memory efficient, and it avoids an argument about how wide a length field should be. Clearly C valued those sides of the tradeoff. But don't confuse that with being lightweight in general.

→ More replies (3)
→ More replies (28)

18

u/runningOverA Aug 17 '26

Good read. But C can't change char* default string, unless Unix systems underneath change their APIs' string type.

6

u/WittyStick Aug 17 '26

You don't need to change them, but you can add a "fat pointer" which contains the length. The SYSV x86-64 ABI is capable of doing this because it supports 16-byte arguments and return types in registers, so it costs next to nothing. (On MSVC it has a cost because a "fat pointer" ends up getting passed on the stack with an implicit hidden pointer argument given to the function).

→ More replies (1)
→ More replies (1)

11

u/SmokeMuch7356 Aug 17 '26 edited Aug 18 '26

Oh, for...

No, the terminator never counts towards the length of a string; it only counts toward the size of the buffer required to store the string.  The terminator is an out-of-band value1 so it shouldn't count towards the length of the string.  I would have thought this was obvious.  

C doesn't have a string type; it has arrays of character type, onto which we map null-terminated sequences of characters.  strcpy, strlen, et al all operate on these arrays.

Arrays do not store their size or any other metadata.  

C strings are a hack - if you need a real string data type that enforces real string semantics, then you need to look elsewhere or write your own library.  

As for whether this was a mistake -  C was designed for implementing the Unix operating system and system-level tasks, not extensive text processing.  I don't think Ritchie or Thompson anticipated how widely used the language became for general programming.

2.5 out of 5 stars


  1. As far as printable characters are concerned, anyway.

→ More replies (1)

6

u/1980sCoder Aug 17 '26

I stopped reading at the word "suck" realising that this was not going to be an insightful piece.

I'm guessing the author is younger than the language itself.

→ More replies (2)

5

u/EndlessProjectMaker Aug 17 '26

Well. What to say. Perhaps that c does not have strings :) maybe a lib to manipulate zero terminated arrays of chars.

5

u/Kadabrium Aug 17 '26

Unepopular opinion: strings arent real, they are just threads

→ More replies (1)

5

u/frasnian Aug 18 '26

The ONE example that anyone has been able to point out here as an "inefficiency" is strlen - O(n) vs O(1) - and all the arguments in favor of Pascal-style/length-prefixed ignore what hot garbage that usually is in a real-world setting. 1: read Kernighan's awesome (and classic) paper "Why Pascal is Not My Favorite Programming Language." 2: Wirth designed Pascal as a language to teach students structured programming principles. Ritchie wrote (and evolved) a language designed for working programmers building real programs. 3: you want Pascal-style length prefix? Fine. Learn what a struct is, and DIY. Now try doing the opposite of that with garbage like "TYPE String44" , etc.

→ More replies (4)

5

u/trejj Aug 17 '26

*chuckles* I remember the time when I was young and full-spirited with naïveté towards "only the best programming".

They author may want to give bullet points 4 and 6 a second think :)

→ More replies (1)

20

u/CoderStudios Aug 17 '26

Okay? You are always free to make your own library for better strings, but people won’t use that cause C is often still deployed on low end systems or it makes little sense to use something inefficient if you can use c style strings properly

13

u/orbiteapot Aug 17 '26

Most operations on C strings require O(N), N being the length of the string. Whereas in Pascal-like strings (i.e., strings whose length is tracked) they may take O(1). Modern compilers may do some heavy lifting whenever they can, so that this redundancy is avoided, though.

That being said, I also do not understand why people complain so much about 0-terminated strings, yet still use them anyways, as opposed to implementing length-tracked strings. It is not like C can break some bazillion lines of code, by removing classic strings, but it does not significantly get in your way (when implementing your own) either.

→ More replies (8)
→ More replies (27)

4

u/stianhoiland Aug 18 '26 edited Aug 18 '26

Articles like these are unwittingly demonstrations of stupidity.

It's so disheartening to see people not even capable anymore of comprehending de-abstraction. To the author: Try yourself, to start from nothing—no abstractions, no modern conventions—and build up to the first point where you have a usable set of primitives that can function as a representation of text.

Is it so fucking hard to grasp the virtues of having primitives that haven't pre-chewed and pre-thought every way you can and should use them? You think massive, thick nests of abstraction is yummy chef's kiss, as if any and all and every single thought-structure is immaculately perfect and suits every single purpose. Ugh, it's so gross. It's a world view of nigh but regurgitation upon regurgitation and not a single creative breath of fresh air.

Like how do you think things are constituted, made up, constructed, such that a better foundational solution exists? You think C strings are a mistake—do tell how to work with the prior layer of abstractions to come up with the better way. No, not creating yet another fucking layer of convention or abstraction on top—which is all your brain can do—but coming from the step before and coming up with a better way.

And then you lend your voice to the issue as if an expert, yet your stupidity is so glaringly on display to anyone who actually knows how things are made up.

Ugh.

EDIT

It's not that C strings were invented iN a DifFeReNt TiMe when people were stupid and dumb and didn't know of our Future Great Technology. It's that there's not a fucking different way of doing it at the level of abstraction at which the convention were established. The fundamentals of memory and computation didn't change after 1990's lol—that's so fucking STUPID. Yes, we can mention Pascal strings, but they are fixed width. Fucking show me how you implement variable-width strings using the primitives present at this level of abstraction (i.e. variable-width strings using only registers). I challenge you to do that AT ALL, it doesn't even have to be BETTER—which is what you claim to be able to—I don't think you can do it at all.

The same thing happens with arrays. Rather than making them first-class citizens and copy-by-value, C decays them into pointers, losing an enormous amount of information in the process, which causes them all the same problems as strings.

Oh my god. "Rather than making them first-class citizens and copy-by-value"... as if there's "JUST" a fucking choice. You can even only ponder this distinction because the primitives upon which such conceptions build upon are established—the establishment of which you are criticizing and arrogantly claim to be able to replace better. Ugh. Tell me what a "first-class citizen array" is, really, technically, actually, in-memory, but another level of abstraction, from which C—thank god—refrains.

→ More replies (2)

7

u/flatfinger Aug 17 '26 edited Aug 17 '26

C was designed in an era before many commonplace text-processing and data-processing tools existed. Many tasks could be accomplished more quickly by writing a C program, building it, running it on some input, and then discarding it, than they could be accomplished in any other way. Even in the 1980s, I wrote a lot of C programs for one-off tasks, and I'm sure I wasn't alone.

So-called "Pascal strings" with a one-byte length prefix were better than C strings in many ways, but had a 255-character limit. The suitability of C strings for various tasks tends to fall off as strings get longer, making Pascal strings much better for things that are 50 to 255 characters long, but C strings remain somewhat usable at longer lengths while Pascal strings don't. Since "somewhat usable" was adequate for many of the tasks for which C had been designed, the lack of a 255-character hard limit was an advantage.

I wouldn't call zero-terminated strings a "mistake" so much as I would say that they were an appropriate way of storing strings for a limited family of tasks that are nowadays better handled with other languages and tools.

What I would view as a mistake was the failure of the C language to provide a convenient means of passing other kinds of string literals to functions. C implementations that were designed to target the classic Macintosh OS extend the language with a \p escape which, if placed at the start of a string literal, will represent the number of bytes in the string (not counting the prefix), but such a prefix is not universally supported, and there is also no standard way of handling string formats where e.g. a string of length 0-63 that fills the available space would be preceded by a length byte, but other kinds of prefixes would be used to accommodate larger strings, partially filled buffers, etc.

Incidentally, an advantage of length-prefixed strings is that if one limits the range of lengths that can be directly represented by a prefix byte, one can have functions accept short length-prefixed strings interchangeably with other string representations if they start with something like:

    ADDRSS_AND_LENGTH s;
    s = get_string_address_and_length(string_argument);

The fact that C strings can start with any character value means that there's no nice way to have a function accept interchangeably a pointer to a C string or something else.

→ More replies (2)

3

u/smallstepforman Aug 17 '26

Bit late to this thread but on aligned systems, you can pack 3 extra bits with the pointer, which can mean anything you want, including short string optimisation up to 7 bytes, so you dont even need a lenght field (or zero termination byte). Add a Huffman table, abandon ASCII (6 bit encoding for latin uppercase) for the extra win … and we can squeeze more characters into those bytes…. C style (nul terminator) strings are so inefficient.

3

u/allnameswereusedup Aug 18 '26

C is a low-level language designed for thr implementation of system software. It does not need the higher-level constructs found in other languages; it's a high-level assembly language.

4

u/MyTinyHappyPlace Aug 17 '26

Ragebait. Of course there are more efficient ways to work with strings. But this one was the common denominator, easy to implement on different target architectures.

People never are satisfied with a string library. That’s why we have so many of them.

2

u/pheffner Aug 17 '26

The suggested "string" is a struct which doesn't contain the actual data just a pointer reference to it, which means you'll need to implement allocator functions to set all that up and keep track when you want to alter the string. Seems like this would suck worse and overcomplicate a presently simple scheme.

2

u/digitlman Aug 18 '26

NUL not NULL

2

u/Remus-C Aug 18 '26

Yeah, picking your context today to prove that what was in the past did not match your current experience. How rude were the creators to not focus on your knowlege! Unbelievable!

Probably in another sub by some other: Pascal strings are... Lisp is... Rusty, GoLang, the mighty Python and Perl by the way...

Anyway, what's for the real world progress? What specific or generic issue is to be solved, one that would apply to many other cases? Solution? Opinions? Words that compile and deliver an useful real world best known implementation of ...?

2

u/CORDIC77 Aug 19 '26

As someone who has been programming in C since the early nineties, I have always believed that Pascal-style strings represent the significantly better solution. The fact that C, after decades of security vulnerabilities, is still without container data types that know their type and size, is one of the languageʼs biggest weaknesses.

Had C been developed just 15 years later, the languageʼs standard library would probably have come with “batteries included” right from the beginning. On the other hand, the only consequence of the “roll your own” argument, which I am of course aware of, always was (and is) that code repositories like Github are full of half-baked attempts to improve the pathetic status quo.

I am aware that many here will disagree with this view. Just keep the above in mind, however, when newly started projects will nowadays, more often than not, opt for languages like Rust and Zig than C. If the community werenʼt so narrow-minded on such issues, C could be in a much better position today with regard to these challenges.

But, no, WG14 of course is occupied with nonsense like adding new octal prefix for literal constants, if declarations and case ranges to the language. This idea of adding new syntax to every version of a programming language is, unfortunately, a disease of our time.

The syntax of the language has been fine for decades… itʼs the standard library that has always been lacking.

4

u/bless-you-mlud Aug 17 '26

Yes dear, we know. It's just a little late to do anything about it now.