I agree with that. Recently I started reading "Writing an interpreter in Go" and thought I'd follow along using C.
From the first chapter, the Go code starts using strings as a short-cut to represent tokens . In other languages this is trivial because strings are very easy to create, resize, change, etc. Using C, this became an issue though, as using strings became a roadblock where I started having to implement different solutions rather than focusing my attention on the contents of the book.
Storing the length of a string alongside the string is a viable option in C, it's up to you to do it yourself though (whereas Java and almost every other language does it for you). See https://github.com/phusion/nginx/blob/master/src/core/ngx_st..., used by NGINX.
Which breaks down every time a C API needs to be called, and that linked API still has plenty of functions with separate pointer and length parameters.
You can do it like in Free Pascal/Delphi: store the string both length-prefixed (for fast length access and bounds checking) and zero terminated (for passing to functions that expect zero terminated strings).
If the callee is meant to corrupt it then it is your fault for misusing it (unless the corruption is intentional and you plan to recalculate the length after calling it). If it wasn't meant to corrupt it then it is a bug and if it is in your code then it is your fault for not using the string functionality that you already have in place to avoid the bug in the first place. If it is not in your code, as long as you had to use it you'd have that bug regardless of what language or framework you used since it is out of your control and there isn't anything you can do about it.
I was actually more looking for practical issues. Usually the code that I write doesn't even handle strings a lot. Maybe I'm just using other languages for when I do that or maybe I'm using other approaches where others would use strings or maybe I just subjectively don't find them so bad as others. I'd just like to see exactly what people are complaining about so I could find out why I usually don't.
Like, apart from tons of well known bugs, and vulnerabilities caused from string manipulation? How exactly did you miss news items, reports, posts, university lectures, books, and even your own personal experience, on them?
Or is the insinuation that we are hand-wavy about it, and you doubt the existence and scale of the problem? It's a well researched, well established problem, known for almost half a century.
3 - C brags about performance and is probably the slowest language to compute string length
I have a feeling that of all axes of performace C cares the most about memory overhead. Then the obvious idea is to have it at exatly one byte per "simple" string, and you get to pick the class of programs that can't get away with that default string type:
• One-byte terminator: complicated text-handling application with a lot of (longer than a couple of pointers on average) string slices.
• One-byte length: anything that needs strings longer than 255 chars.
And then of these two solutions you pick the obviously more general one. What could possibly go wrong?
What was made around them was only ever the gorilla with jungle thing. Fine for small programs, or larger ones within walled gardens. But not fine for infrastructure work.
these seem like tradeoffs which are straightforward to understand, which allow for simpler ABI & runtime. sure, the "UX" of the language suffers compared to e.g. Python, but at least the mechanics are easier to understand. If you want Python style string handling in C you could just use the Python C-API.
>If it was a bad thing, UNIX wouldn't be so widespread.
Strange argument. Javascript is widespread, COBOL was widespread, Windows is widespread, X86 is widespread. Widespread doesn't mean good. UNIX was a disaster, and the whole family of UNIX-like OS spent decades just for mitigate its errors and faults.
> To make substring in some other languages, you need to store pointer to beginning of the substring and length of substring.
Plus a pointer to the beginning, plus a reference counter as the user expects it to manage lifetime. In C this is the user's job. Where they know the life time is guaranteed they can optimize.
> That substring won't remain valid without copying it.
Unless it's not modified? And unless, when modified, that shouldn't be its new value?
C Strings (nul-terminated) are the right approach for static storage of small static strings (like strings literals in the source code) since they have low overhead, and "substrings" aren't second-class citizens.
For dynamically allocated strings that won't be modified after creation, the right approach is using a large memory chunk that is shared between many such strings, plus two indices for offset / length (or just offset if it's text that can be terminated with a sentinel).
Having a short string of about 10 characters allocated as a dynamic object in its own allocation is wasteful. Slow to allocate and has about 2x to 3x overhead. This approach isn't good for applications that store a large amount of data.
> O(N) is not the same as O(1).
Don't call strlen() in situations where the strings are large and you need to know the length ahead of time, and running time is paramount. Instead, store the length.
You don't need profiler feedback for these things, just back-of-the-envelope calculations. How much data you would like to store and process is not something a profiler can answer for you.
I can tell you that in one of my programs, the difference between garbage collected strings and optimized ones (actually, strings converted into unique integer handles immediately) for one of my projects (SAT solver in Java), handling a few million variables, was something like a second until completion vs a couple of minutes before the garbage collector finally dies due to lack of oxygen, losing all data computed up to that point.
I agree, but C (the language) doesn't even have the concept of a 'string'. It's just the convention how some C standard library functions interpret an array of bytes with a zero at the end.
At least in C it's quite obvious that strings are not trivial if you want both an intuitive way to work with strings, and high performance. The C++ std::string type is neither intuitive to work with, nor does it allow to write high-performance code.
For string processing it's really better to use another language with different trade-offs.
Many would also agree, that means nothing. Personally i dislike C++'s strings... and the rest of STL, which i view as one of the worst standard library APIs in wide use.
More often that not I find myself missing C-type strings in other languages. Being able to just walk through the characters and manipulating them is something I found rather ugly in python for instance. The NUL character is in my experience not so terrible, you typically have null pointers at the end of a linked list or whatever as well and nobody complains about that. Now I have to admit I had a bug recently that took me longer to fix then I would like to admit because I wasn't walking a string right, but usually I have very little trouble with them.
When was the last time you iterated over a string of unicode points and said you know what would be handy right now? If these code points were split up into arbitrary and unusable bytes of memory.
Well, ä is a character in German. You can either write it as LATIN SMALL LETTER A WITH DIAERESIS, or you can use COMBINING DIAERESIS and a. When you iterate over the German word Mädchen as Unicode code points you might be confused. Other languages do much crazier things.
It's been a long time since I wrote C, but the main problem in my recollection is that the standard library is not intuitive. Something simple like take a couple of arbitrary strings, concatenate them and return the result without leaking memory and not causing buffer overflows is not as trivial as it should be.
I don't think is a huge problem per se, though, you can just use a string library.
>More often that not I find myself missing C-type strings in other languages. Being able to just walk through the characters and manipulating them is something I found rather ugly in python for instance
If what you say if you want mutable strings, many languages have those and you don't need anything like "NULL" to have them (and you can use a bytearray of the string in Python, though Unicode complicates this).
>The NUL character is in my experience not so terrible, you typically have null pointers at the end of a linked list or whatever as well and nobody complains about that
That's not the same thing at all. The linked list is comprised of structs with next fields, that can be null or point to something. Your program can handle either just fine, as both are valid cases (a linked list expects to find the NULL guard at the end but also expects a non-NULL next pointer if the node is not the last one, so will handle both).
OTOH, if an incoming string doesn't have a NUL byte your program will crash/corrupt memory/worse. On top of that, you need to remember it to add it/make space/for most string manipulations. Strings are not expected NOT to end with NUL, and when they don't there's no way you can mitigate it, except to set arbitrary limits to how many characters you consider.
> OTOH, if an incoming string doesn't have a NUL byte your program will crash/corrupt memory/worse.
how are linked lists different? if the last node contains garbage for its next pointer, the outcome will be exactly the same. it's a bit more rare to encounter an "unterminated" linked list, but I've seen it happen plenty of times deserializing a linked list from disk or if the programmer just forgot to initialize the pointer. c strings basically are linked lists with an implicit next pointer.
Why is this comment being down voted? Not everybody here is familiar with C string manipulation, if you down vote or complain at least give more detail that "it sucks".
> I'm guessing because an off-by-one or an extra skip might mean you miss the end of the string and go off into la-la land feeding whatever garbage happens to be in memory to your parser? That would mostly be a C issue (as it has no string abstraction at all).
>Why is this comment being down voted? Not everybody here is familiar with C string manipulation, if you down vote or complain at least give more detail that "it sucks".
Well, if someone is not familiar, why do they read a subthread on the matter?
Shouldn't they better start with a tutorial on C/C strings?
Even if people on this thread gave arguments, how would they (not familiar with C and C strings) would evaluate them? They could be totally bogus.
I am finding hard to imagine not seeing the difficulty here, so instead I’m just gonna point out simple operations like stripping whitespace, splitting strings on a character pattern, changing case, dealing with character encodings, regex matches all require manually iterating and mutating or copying strings and in the case of regexes require compiling and auditing various libraries. The abstractions other standard libraries have used, such as rust, make it much easier to simply express the string operations as high level operations and spend your time elsewhere while retaining relatively high levels of performance. Often, string processing is not in the inner loop and does not benefit from things like combining multiple string operations into a single pass, traditionally a thing that might make c perform better all other things being equal.