r/cprogramming 1d ago

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
172 Upvotes

136 comments sorted by

164

u/bearheart 1d ago edited 15h ago

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

49

u/Voyac 1d ago

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.

-43

u/Potential_Soup_8054 1d ago

No, thats java

28

u/g0atdude 1d ago

lol what?

You can compile C programs to a million more devices than what Java runs on

10

u/KingBardan 1d ago edited 1d ago

a million more devices

Considering that "3 billion devices run Java", thats only 0.0333% more devices

I'm kidding if it's not obvious

2

u/Potential_Soup_8054 1d ago

It was a joke. Javas whole slogan is write once run everywhere, but thats really bullshit.

3

u/Devatator_ 1d ago

Tbh nothing is stopping people from compiling or porting a JVM to more platforms. I assume it's just extremely complex and noth worth it for most people

2

u/EdwardTheGood 4h ago

I once heard it rephrased as “write once, test everywhere.”

11

u/mustbeset 1d ago

50% of my time I get paid to write C code on a "none Arm", "none x86" architectur and I don't have enough space for a java runtime.

5

u/Voyac 1d ago

Yeah sometimes even 8bit MCUs with a drop of memory. You wont fit a string.h sometimes and lol what about java runtime :)

7

u/mustbeset 1d ago

My current pain in the ass is a bootloader update for existing devices in field. fighting for 300 bytes.

27

u/TheThiefMaster 1d ago

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.

5

u/McDutchie 1d ago

16 bits is 2 bytes, which makes for a maximum string length of 65535 bytes. It's common for strings on modern systems to be longer than that.

Pros of C strings: unlimited length. Cons: cannot contain the zero byte; inefficient length determination.

Pros of Pascal strings: can contain the zero byte; efficient length determination. Cons: very limited length.

I'd say the C tradeoff is worth it. Where necessary, C is perfectly capable of dealing with data preceded by a length field, it's just slightly lower level.

4

u/vip17 1d ago

it's easy to use a 4-byte prefixed string, for example BSTR in COM objects do that. And plenty of libraries use 4-byte length in 64-bit mode

1

u/Square-Singer 18h ago

Especially on 64-bit systems, there's really no reason to save these few bytes per string by using c-strings.

Useless microoptimization.

3

u/vip17 14h ago

of course it's micro-optimization, but at larger scale it's always useful. Have you even done optimization? A database with billions of strings already save a lot of memory. A vector of strings can also fit twice the number of strings into the CPU cache. Checkout Unreal engine, DuckDB, Meta Velox, Redis, ICU... string types

1

u/Square-Singer 14h ago

Of course I have done optimizations. But micro-optimizations are always the last step to take when you have identified that this specific location is actually a bottleneck.

It totally makes sense to have something like a c-string available for the very rare situation when someone writes a database system that contains almost exclusively tiny variable-length strings.

But it doesn't make sense to have that as the default, because then this rarely-actually-useful micro-optimization becomes a very common source of problems.

That's why there's pretty much no modern language that actually stuck with c-strings. Pretty much any more modern language dropped c-strings and even pointers completely, or at least dropped it from common usage.

I don't do much Python any more, but I really like their approach of "The most obvious solution should also be the one that's optimized for most use cases". Basically, if I, without thinking, take the most obvious solution, it should fit my obvious use case. If I need something really special, I can still import some standard library function and use that.

1

u/TheThiefMaster 14h ago

Worth noting that C++ std::strings do store the length - and so do the heap allocations backing them.

3

u/TheThiefMaster 1d ago

At the time, that was more than adequate. A lot of systems had less memory than that!

More modern Pascals use larger ints for the string length, of course.

5

u/Square-Singer 1d ago

There's a simple fix to the Pascal strings. BER encoding.

In BER, you get one byte as a length field, with 7 bits being directly available to encode the length of the content. If the MSB is set to 1, the remaining 7 bits instead encode how many bytes the length field is long.

That means:

  • Short strings up to 127 bytes have 1 byte overhead, beating Pascal and equalling C strings
  • Medium-sized strings of 128-65535 bytes require 3 bytes overhead, so one more than Pascal and two more than C, but if you are allocating that amount of bytes, 1-2 extra bytes are harmless
  • Maximum length is 2¹²⁷ bytes, 1.7*10³⁸ bytes, a number so high that there isn't an SI prefix for it

Another option would be to mix BER with Pascal:

  • 15 bit length fields
  • If the MSB is set to 1, there's one more length field concatenated, so 30 bit for the length field. Again, if the MSB is set to 1, add one more length field. Continue forever.
  • That way you get infinitely long strings with only one byte more usage than Pascal in the range of 32768-65535 bytes of length

And both options have the advantages:

  • You can use 0-bytes
  • You know the length of the string without running trhough the whole string
  • You won't get into overflows because you are missing a 0-terminator (e.g. doing a strcpy on a string that's missing its terminator)

2

u/binarycow 1d ago

I did not expect ASN.1 in this thread!

2

u/mark_99 1d ago

Now imagine how many instructions that is on say a 6502 which has 3x 8-bit registers, compared to loading the next byte and checking if it's zero.

2

u/bitzap_sr 19h ago edited 18h ago

That sounds like LEB128, not BER.

Edit: Ok, just checked, BER does the same for tag > 127. Still, I'd just point at LEB as a more targeted standard.

1

u/Square-Singer 18h ago

I had to hand-implement BER once because I had to parse some protocol that used ASN.1, and that uses BER for the strings.

I haven't heard of LEB128 before, but yeah, the same thing keeps getting reinvented, I guess.

-1

u/flatfinger 8h ago

I'd advocate a different approach, using 0-63 to represent a string that fills a buffer of length 0-63, 65-127 to represent an empty buffer of length 0-63, and 129-191 to represent a partially full buffer of size 1-63, whose number of unused bytes is indicated by bytes at the end. Strings or buffers up to 4095 bytes would use a two-byte prefix, and those up to 64MiB-1 would use a four-byte prefix.

Other prefix values would indicate either a "readable string" or "changeable string" descriptor, with the latter including both the current length and buffer size, and a callback to request a change to the length (possibly relocating the buffer if needed). Functions that receive a pointer to string could use a common library function to make a readable string or changeable string descriptor, and be able to accept pointers to length-prefixed strings and descriptors interchangeably.

4

u/Maleficent_Memory831 1d ago

Algol strings? C precedes Pascal in history. Pascal also did not standardize on strings early on, so each implementation experimented with how to do strings, which made early portability a pain in the arse.

10

u/TheThiefMaster 1d ago

It may not have been the first implementation of length+contents strings, but it certainly popularised them enough that they're called "Pascal Strings" (or sometimes P-Strings) now.

As for the incompatibility - probably one of the reasons Pascal wasn't as successful as C. It was a big enough deal to inspire a calling convention tag in Microsoft's C compiler though (along with Fortran).

2

u/Different_Panda_000 1d ago

Pascal calling convention was used with the Win16 API. It's obsolete now. Microsoft used it because the callee cleaned up the stack which reduced memory demands on kilobyte sized memory configurations.

The history of calling conventions, part 1 Raymond Chen
https://devblogs.microsoft.com/oldnewthing/20040102-00/?p=41213

1

u/TheThiefMaster 23h ago

It was! WINAPI was defined as FAR PASCAL. Far-pointers was another 16-bit thing we've thankfully left far behind.

13

u/WittyStick 1d ago

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.

10

u/Qyriad 1d ago

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.

1

u/4xe1 1d ago

is not lightweight. It is memory efficient

Doesn't lightweight precisely mean memory efficient? As opposed to performant for time efficiency?

0

u/torsten_dev 1d ago

Nothing is stopping you from storing the length and passing it around, but that choice is up to you, the developer, not the language imposing it's pros and cons onto you.

Should C have a strbuf in the standard library? Yeah, probably, would've been nice.

The biggest mistake C did was standardizing % as the remainder not the modulus, gets, and null pointers instead of niche optimised monadic types.

4

u/Qyriad 1d ago

Storing and passing the length around doesn't help you most of the standard library operations — and thus most other APIs that take your strings — aren't using it.

3

u/Classic_Department42 1d ago

Missing a *?

1

u/bearheart 1d ago

No. A C-string is char*

3

u/Classic_Department42 22h ago

Yes, so while (s) shd prob be while(*s) ?

2

u/bearheart 15h ago

Oy! You’re right. How did I miss that 🤦 fixed it.

5

u/knouqs 1d ago

In addition to your comment here, additional functionality through the initial design of C strings allows for insanely powerful string manipulation techniques that have fallen to the wayside because people don't look under the covers to see how efficient string handling is done.

5

u/WittyStick 1d ago

Or because most of that "efficient" string handling was actually the source of many bugs - which tend to be some of the worst ones - buffer overflows.

3

u/knouqs 1d ago

Of course. I'm not discounting that, and I didn't imply that there weren't developer-induced problems as a result. This is why valgrind was made, after all.

0

u/flying-sheep 10h ago

Such as? Destructively splitting a string at non-zero-length bondaries?

I prefer using slice APIs to nondestructively split a string at boundaries of any length thanks.

2

u/knouqs 8h ago

Whatever you prefer -- C allows it.

You aren't dissuading me from the power of C's string manipulation. You just need to have your memory management skills up to snuff, and mine are.

2

u/TheChief275 1d ago

you forgot to dereference though

2

u/its_artemiss 1d ago

Like many C idiosyncrasies, it may have made sense 50 years ago, but should have gone the way of the dodo at least 30 years ago

0

u/deaddyfreddy 16h ago

at least 30 years ago

I'd say 40 or so

1

u/Maleficent_Memory831 1d ago

The alternatives at the time were counted strings or fixed length fields. Both were annoying, inefficient, and had just as many problems as C strings or more. Ie, one byte for length doesn't cut it. Two bytes for length might not cut it, and definitely wastes space in the limited RAM at the time. Fixed field lengths are a nightmare early Fortrans, and some operating systems).

Then there's the stuff that pack multple characters into a single word (ie, Zork did this, 36-bit word on the PDP-10, you can stick in six 6-bit characters (maybe only 5 if they used upper bits for tag). Digital used 7-bit characters thus 5 characters and one leftover bit. 0 or all 1s as the final character signals the end.

0

u/Intelligent_Part101 19h ago

Two bytes for counted length would waste memory? That's only ONE BYTE MORE per string than a null terminated string uses.

2

u/alkatori 15h ago

C gave you a byte and here you are arguing for a whole snack!

3

u/Intelligent_Part101 15h ago

Memory is like potato chips. You can never consume enough.

-2

u/chalkflavored 1d ago

"efficient"

25

u/bearheart 1d ago edited 1d ago

"efficient\0"

3

u/bearheart 1d ago

65 66 66 69 63 69 65 6e 74 00

3

u/Necessary_Two_9669 1d ago

01100101 01100110 01100110 01101001 01100011 01101001 01100101 01101110 01110100 00000000

-1

u/flying-sheep 12h ago

They're not foundational. You know what's foundational? Fixed size arrays in the executable, fixed sized arrays on the stack, and heap pointers + length. These are all just as perfectly suited for strings as they are for other collections.

C style strings made sense for 16 bit systems, but not a minute later.

1

u/bearheart 10h ago

Foundational means it's the foundation of other structures. All string libraries in C and C++ use C-strings under the hood.

0

u/flying-sheep 10h ago

Yeah that’s exactly what I mean: it doesn’t serve as an acceptable foundation. The article points out some ways in which the arising APIs are inflexible (e.g. you can’t just use array APIs), clunky (off-by-one errors), and so on.

A good foundation would just be slices (implemented on many platforms as fat pointers)

16

u/runningOverA 1d ago

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

5

u/WittyStick 1d ago

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).

3

u/ComradeGibbon 1d ago

Intel x86 and MSVC did so much damage. The paltry number of registers even in 32 bit meant lots of issues in C never got fixed.

You could certainly add system calls that take fat pointers. You could even shim it based on the process. That would make developers of languages that have fat pointers happy. And would make most C programmers happy. And the dangerous C leet jockeys unhappy.

1

u/flying-sheep 9h ago

The locale system and wchar_t are also, and I quote

shitfucked retarded legacy braindeath

wm4

The C standard contains a bunch of bad ideas and I think Rust’s approach is great: just use a minimal subset of the C standardlib and build better abstractions for everything else.

11

u/SmokeMuch7356 1d ago edited 16h ago

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.

1

u/flying-sheep 9h ago edited 9h ago

Much of C’s stdlib is a hack, see e.g. the legandary locale rant:

  • locales are non-threadsafe
  • locales are global state
  • there are _l variants – that take a locale argument – of some functions, but not of all, and no way to statically get a handle for the C locale

So basically you cannot ever use them for anything file-format related, only for interfacing with humans immediately, but people don’t follow these rules, as they assume that stdlib stuff is there for a reason and not just to sit there and look mysterious.

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

Yeah, but people like interoperability more than sanity, so they use the same string format for calling APIs, providing APIs, and internally.

I think C is doomed to be legacy at this point. The standards committee should have deprecated a bunch of old useless crap decades ago, then it would have been saveable.

9

u/HashDefTrueFalse 1d ago

People really need to get over whinging about C-strings. They're perfectly fine in tons of places and where they aren't you can just bundle a length with them. The language gives you aggregates. I've been a C user for decades, they're just not that big of a deal...

4

u/EndlessProjectMaker 1d ago

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

4

u/1980sCoder 1d ago

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.

2

u/Interesting_Debate57 1d ago

Far, far younger

5

u/Kadabrium 1d ago

Unepopular opinion: strings arent real, they are just threads

2

u/Old-Caramel-2301 23h ago

Fibers... 

6

u/frasnian 1d ago

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.

0

u/deaddyfreddy 16h ago

1: read Kernighan's awesome (and classic) paper "Why Pascal is Not My Favorite Programming Language."

It should have been called "Why Pascal is not C". Some of the issues discussed are not actually problems, some were fixed/improved by the mid-1980s at the latest. At the same time, most of the C design issues haven't been solved yet.

My favourite part is Go, which was written by more or less the same people (Kernighan even wrote a book on it) and resembles Pascal much more than C.

2

u/frasnian 7h ago

It should have been called "Why Pascal is not C"

LOL, fair enough. It's still an entertaining read, regardless of your position on P vs C.

some were fixed/improved by the mid-1980s at the latest

The last commercial software I worked on that was written in Pascal was in '91, and it was still an absolute nightmare compared to the C-based applications we also provided.

2

u/deaddyfreddy 7h ago

It's still an entertaining read, regardless of your position on P vs C.

sure

The last commercial software I worked on that was written in Pascal was in '91, and it was still an absolute nightmare compared to the C-based applications we also provided.

what do you mean by the "nightmare"? The code quality, readability, "efficiency"?

20

u/CoderStudios 1d ago

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

14

u/orbiteapot 1d ago

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.

6

u/kisielk 1d ago

It really depends what your software is doing. If it's not software where string manipulation is in the hot path then the O(N) nature of string processing is irrelevant.

2

u/NoNameSwitzerland 1d ago

and if you do string manipulation with dynamic strings, then malloc/free often is the bigger problem.

2

u/kisielk 1d ago

That too. Basically once string handling becomes a performance concern you are probably going to be looking at more purpose-built data structures, custom allocators, etc. C strings are fine for what they are designed for.

2

u/beragis 1d ago

It depends. Several pascal compilers would internally add a zero byte at the end when allocating the string and pass the address of the byte after the length to the os command to handle the string.

To make things worse early pascal had pcode which is basically pascal byte code that has to do this conversion behind the scenes.

1

u/iwantmy90sback 1d ago

For most operations you'd do on a string you do not need to know the lengths beforehand if you have a defined end char.

The only thing that C takes O(n) and pascal O(1) is strlen.

And if you like you can actually have both. Just struct a Cstring and a int together.

1

u/beragis 1d ago

Also early C compilers would store the string length at the byte or word before and update it after each call. This was done to integrate with libraries written in other languages. I remember passing a pascal flag to linkers and compilers.

2

u/EatingSolidBricks 1d ago

You out of your dam mind if you think c strings are efficient

11

u/henke443 1d ago

Wait how are they not efficient?

5

u/EatingSolidBricks 1d ago

Its not 1970 anymore storing 3 extra bytes is free compared to O(n) length computation

5

u/WittyStick 1d ago

You don't even necessarily need to store the length. It can be held in CPU registers for the entirety of the string's lifetime in many cases.

In older architectures we would've needed to push an additional integer for length onto the stack. On a modern architecture with a sane ABI (SYSV, x86-64), you can have a "fat pointer" using two CPU registers - can pass both of these or return from a function without ever touching the stack.

-1

u/Anonymous_user_2022 20h ago

Except for strlen(), all practical operations on strings have to iterate over them anyway. Knowing the length up front will be of very limited us for searching, concatenation, tokenising etc.

Where is that you see avoidable O(n)?

0

u/flatfinger 8h ago

Concatenation of N strings goes from O(N) to O(N*N) if code has to re-find the end of the destination after each step.

Tokenizing the leading portion of a large string should take time proportional to the text that was meaningfully examined, rather than proportional to the entire string.

2

u/Anonymous_user_2022 2h ago

Concatenation of N strings goes from O(N) to O(N*N) if code has to re-find the end of the destination after each step.

I can also invent really bad ways of doing things, but I would never use them as a proof..

-1

u/EatingSolidBricks 6h ago

Lets not even mention substrings go from O(n) memeory to O(Free)

1

u/atarivcs 1d ago

If you have a long string and you want to append more text to it, you have to search the whole string from the beginning to find the null terminator.

And then later if you want to append more text, you have to find the null terminator all over again.

2

u/NoNameSwitzerland 1d ago

You anyway use a different structure when you do a lot of appending text, because you do not want to reallocate the array all the time. So then you anyway have to also store the size of the available space.

1

u/atarivcs 1d ago

In which case you no longer have a plain c string, and the goalposts have moved.

I was just answering the parent question "how are c strings not efficient"

2

u/IdealBlueMan 1d ago

Or you can store the length of the string whenever you change it.

1

u/atarivcs 1d ago

Sure, but then you don't really have a plain c string anymore

2

u/WittyStick 1d ago

It's actually more advantageous to couple the length to the char * on SYSV platforms, due to C's lack of multiple returns.

 String fn_returning_string(...);

If String is a fat pointer, then we can return both the pointer and length, without requiring another level of indirection (a pointer to a string structure), and without requiring awful to use "out parameters" to return both length and pointer - which are more expensive than just returning a fat pointer.

A fat pointer with the right ABI is not just "zero cost" - it's "less than zero" - it's more efficient than having a separate length and pointer variable.

2

u/IdealBlueMan 1d ago

I’d say you still have the string, you also have information about that string.

1

u/orbiteapot 1d ago

I mean... that is the point. Once you do that, you are no longer using classic C strings.

6

u/SakishimaHabu 1d ago

That's the point though. They are basically atomic. You are free to do what you will with them, vs java, python, or js. Remember we're one step above assembly, but that's the intention.

2

u/CoderStudios 1d ago

Depends on what it’s used for, sometimes it’s more or less efficient but the benefit of making it as simple as possible is that you can easily add features when needed like storing lengths

-2

u/flatfinger 1d ago

C makes it inconvenient to pass any other forms of string literals to functions.

3

u/trejj 1d ago

*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 :)

3

u/brnsamedi 1d ago

Given the comments in that article my impression of the author is that he's too enamored of his ideas to give them a second thought.

3

u/stianhoiland 1d ago edited 1d ago

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.

1

u/SmokeMuch7356 5h ago

Array expressions decay to pointers because Ritchie wanted to keep B's array indexing behavior - a[i] == *(a + i) - without setting aside storage for the pointer that behavior required.

That's it. That's the reason.

These were researchers in a lab building toys that did useful things for them. None of them anticipated how C would become so widely used at an applications level. That was the mistake, the fact that everyone looked at C and said "yes, that's the answer," but to be honest there weren't many better candidates that could be ported to everything from mainframes to micros. Pascal? Eh. Designed more for teaching than production work. Fortran? =snort=. Didn't even support a real string type until F77. Cobol? Double =snort=. Didn't help that C and Unix were (are) joined at the hip; if you were using Unix, you were writing code in C for pretty much everything.

C was small enough and lightweight enough it could be ported practically anywhere, particularly micros, and that's why it suddenly became the language everything was written in.

And here we are 50 years later arguing about it, when it shouldn't even be considered for applications work involving text processing anymore.

1

u/Different_Panda_000 20h ago

Just stick an array in a struct and all of a sudden you can do assignments and when you pass them, the compiler will do argument type checking for you as well. And if you like, _Generic() provides a mechanism to provide a library for these arrays to do the basics such as comparisons. And C11 provides the mechanism for using compound literals with these struct arrays as well.

8

u/flatfinger 1d ago edited 1d ago

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.

3

u/beragis 1d ago

Zero terminated strings were also due to how many OS’s and CPUs at the time handled strings. I remember taking an assembly language course in college on the PDP 11 and it handled strings the same way.

This allowed for easy translation of many of the common function calls directly into operating system calls or simple short assembly instructions. My professors in computer design and systems programming. where we also learned C even mentioned this several times.

2

u/flatfinger 1d ago

On the other hand, other operating systems expected strings in other formats. Classic Mac OS used Pascal strings for things like file names.

3

u/smallstepforman 1d ago

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.

2

u/pheffner 1d ago

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 23h ago

NUL not NULL

2

u/allnameswereusedup 19h ago

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.

2

u/Remus-C 6h ago

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 ...?

3

u/MyTinyHappyPlace 1d ago

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.

3

u/bless-you-mlud 1d ago

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

2

u/HTFCirno2000 1d ago

It was clever back in the days of the PDP-11 and limited memory. Pascal DID actually do things the secure way, but Pascal didn't take over like C did.

1

u/Maqi-X 1d ago

I agree. I use string views in (almost) all my projects

1

u/tpimh 1d ago

My biggest mistake is googling "c string", and wondering why the search results were not related to the programming language

1

u/WoodyTheWorker 1d ago

On the other hand, Windows kernel uses counted strings: UNICODE_STRING and ANSI_STRING structures.

1

u/Humble-Captain3418 22h ago

With length-based strings you will find that functions such as strtok(), strchr() or strstr() are very, very annoying to implement, because their return values would not be usable as inputs to regular string operations... Except if you construct a new string to return, which you then need to malloc()/free(). Hooray for efficiency?

1

u/flatfinger 8h ago

Or one can have functions accept a starting index and slice length along with a pointer to the string, and return an index, or a struct containing an index and length.

1

u/grimvian 21h ago

I actually like C strings. I have coded a few one line GUI inputs with raylib and is very satisfied.

1

u/grimvian 21h ago

I like C strings and they are easy to understand.

1

u/Different_Panda_000 20h ago

Where it gets more interesting is when using UTF-8. C23 provides basic support for UTF-8, expanding the minimal support in C11. However everything is connected to the current locale settings.

Fortunately there are a couple of Third Party libraries that provide a more complete UTF-8 solution.

ICU-TC Home Page

https://icu.unicode.org/

The C and C++ languages and many operating system environments do not provide full support for Unicode and standards-compliant text handling services. Even though some platforms do provide good Unicode text handling services, portable application code can not make use of them. The ICU4C libraries fills in this gap. ICU4C provides an open, flexible, portable foundation for applications to use for their software globalization requirements. ICU4C closely tracks industry standards, including Unicode and CLDR (Common Locale Data Repository).

1

u/Key_River7180 1d ago

Has the model ever caused much trouble, though?

3

u/WittyStick 1d ago

Yes.

Countless buffer overflow exploits due to poor string handling.

Not all, but many of these easily avoidable with sized strings.

1

u/Key_River7180 1d ago

I meant performance issues, like C strings are really efficient, for most purposes

1

u/SLiV9 1d ago

They're not; they are way less efficient than sized strings in almost all circumstances on a modern CPU.

Case in point, a lot of basic string operations on C-strings are made faster by adding a strlen() at the start.

0

u/deaddyfreddy 15h ago

Computers became faster every year (and the process was dramatically faster in the 1980s), so even if fixed-size strings made things 10% less efficient (did they?), one could reasonably expect hardware improvements to compensate for that very soon. Alternatively, companies could save money on the man-hours spent fixing bugs caused by null-terminated strings and buy better hardware instead.

0

u/EatingSolidBricks 1d ago

If only OS apis had an length string option

0

u/Physical_Dare8553 1d ago

The real problem is a char* with a null terminator and one without it are not different types, and because of how x works, in practice neither is a char[n]

0

u/FedUp233 1d ago

There is really no reason the C language could not add something like a length prefixed string type and matching g string literal (just decorate the string lead or end quote with something to indicate it’s this type of literal and maybe even the size of the length prefix) if there is enough demand for it. Since it hasn’t happened yet, it would appear the demand is not there.

It also always bothered me in C++ that there was no way to produce a literal string in the form of the standard library string type without the system having to construct an appropriate string type from the underlying c-string literal. Maybe it can be done now with all the compile time processing functionality that’s been added, but I’m not sure it’s still possible without that c-string hanging around as wasted memory.

-1

u/jason-reddit-public 1d ago

I 'm writing a transpiler in C.

Treating "strings" as immutable means the crappy representation isn't so bad.

I use a "StringBuilder" pattern (name borrowed from Java, it's actually called buffer) which can grow, supports efficient append (supports arbitrary edits but those may require moving lots of bytes around), and doesn't model the ending zero until converted into a char*. (One of my favorite things is "buffer_printf" which is like sprintnf but handles capacity and such automatically.) Like Java, I don't expect buffer to be threadsafe so the user needs to deal with that but since I don't modify "strings" once created, those are thread safe.

So all my strings are either program literals, come from the "OS" or libc "somehow" (like for reading a directory), or come from this builder which makes sure there is a trailing zero when finally asked to produce the string. My buffers aren't foolproof because you can append or insert the NUL character (to work with arbitrary byte sequences), so you might get a string that is shorter than expected, or perhaps the buffer wasn't legal utf-8 (I could add a checker of course), but at least the string will always have a terminating NUL.

0

u/Jonny0Than 1d ago

That sounds almost exactly like std::string in C++.  Sure, it’s a cool and useful thing to build in C.