18 ms·
Unfortunately, much of the pain with C surrounds dealing with strings. It’s been a bit of a theme on Hacker News for the past few days, but it’s actually a pret
by jchw 6y ago
Unfortunately, much of the pain with C surrounds dealing with strings. It’s been a bit of a theme on Hacker News for the past few days, but it’s actually a pretty good spotlight on something I feel is not always appreciated - strings in C are actually hard, and even the most safe standard functions like strlcpy and strlcat are still only good if truncation is a safe option in a given circumstance (it isn’t always.)
(~~Technically~~ Optionally, C11 has strcpy_s and strcat_s which fail explicitly on truncation. So if C11 is acceptable for you, that might be the a reasonable option, provided you always handle the failure case. Apparently, though, it is not usually implemented outside of Microsoft CRT.)
edit: Updated notes regarding C11.
- masklinn 6y ago> Technically C11 has strcpy_s and strcat_s "Theoretically" is the word you're looking for: they're part of the optional Annex K so technically you can't rely on them being available in a portable program. And they're basically not implemented by anyone but microsoft (which created them and lobbied for their inclusion).
- jchw 6y agoI didn’t know that it was Microsoft that lobbied for them; that perplexes me since I thought Microsoft’s version of them were a bit different (for example, I think C11’s explicitly fail on overlapping inputs where Microsoft specifies undefined behavior) and because Microsoft didn’t bother supporting C99 for the longest time. (Probably still don’t, since VLA was not optional in C99, IIRC. I think Microsoft was right to avoid VLA, though.)
- astrange 6y agoVLA syntax can be useful because you can cast other pointers to them - for instance you can cast int* to int[w][h] and then access it as [y][x] instead of [y*w+x]. As a bonus this crashes icc if you do it.
- jchw 6y ago>As a bonus this crashes icc if you do it. I thought this was a pretty funny thing but unfortunately when I tried this on ICC it seemed to compile just fine. Though I am amused by one thing: the VLA version generates worse code on all compilers I've tried. Seems to validate the common refrain that VLAs tend to break optimizations. (Surely it's worse when you have an on-stack VLA though.)
- astrange 6y agoThis does it: https://gcc.godbolt.org/z/5fz8sM https://gcc.godbolt.org/z/5fz8sM I'm not sure if they take bug reports if you're not a customer, but this one goes back at least 8 years.
- julian55 6y agoThey are also implemented by embedded compilers such as IAR and Keil.
- moonchild 6y agoMicrosoft's implementations are distinct and incompatible (and they haven't changed to be compatible with the standard versions because of backwards compatibility).
- loeg 6y agoMicrosoft doesn't actually implement Annex K! Annex K is based on MSFT's routines, but they diverged. So Annex K is portable nowhere, in addition to having largely awful APIs. http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm#:~:text=Microsoft%20Visual%20Studio%20implements%20an%20early%20version http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm#:~... > Microsoft Visual Studio implements an early version of the APIs. However, the implementation is incomplete and conforms neither to C11 nor to the original TR 24731-1. For example, it doesn't provide the set_constraint_handler_s function but instead defines a _invalid_parameter_handler _set_invalid_parameter_handler(_invalid_parameter_handler) function with similar behavior but a slightly different and incompatible signature. It also doesn't define the abort_handler_s and ignore_handler_s functions, the memset_s function (which isn't part of the TR), or the RSIZE_MAX macro.The Microsoft implementation also doesn't treat overlapping source and destination sequences as runtime-constraint violations and instead has undefined behavior in such cases. > As a result of the numerous deviations from the specification the Microsoft implementation cannot be considered conforming or portable.
- Tempest1981 6y agoImportant to note that strcpy_s doesn't truncate, it aborts your app if it fails: > "if the destination string size dest_size is too small, the invalid parameter handler is invoked" > "The invalid parameter handler dispatch function calls the currently assigned invalid parameter handler. By default, the invalid parameter calls _invoke_watson, which causes the application to close and generate a mini-dump." https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strcpy-s-wcscpy-s-mbscpy-s?view=msvc-160 https://docs.microsoft.com/en-us/cpp/c-runtime-library/refer...
- liuliu 6y agoYeah. I just avoid str manipulations in general in C and when I have to, fuzz it ... (but still, the perf cliff is definitely new to learn in the past few days).
- InvOfSmallC 6y agoI teach at university as external lecturer. Teaching strings in C is the hardest thing I have to do every time. The university decided to explain C to first year student without previous experience. My feedback was to do a precourse in Python to let them relax a bit with programming as a concept and then teach C in a second course.
- austinl 6y agoMost of the C I wrote was while in college. I think understanding the question, "why are strings in C hard?" is a good gateway to understanding how programming languages and memory work generally. I agree with you though that teaching C as introductory is probably not the best — our "Programming in C" course was taken in sophomore year. I wouldn't want to use it my day job, but I'm glad that it was taught in university just to give the impression that string manipulation is not quite as straightforward as it's made to appear in other languages. The early days of Swift also reminded me of this problem – strings get even more challenging when you begin to deal with unicode characters, etc.
- c-cube 6y agoIt's also because other languages have better designed strings. D, go, rust, etc. have pointers too but their string handling is based on slices and arrays, which are approximately 10,000 times less footgunny.
- ritmatter 6y ago+1, my university's program seemed to work well with "program anything" (Python), "program with objects" (Java), "program some cool lower-level stuff" (C)
- _the_inflator 6y agoYep, agree. I used a lot of assembler on C64 and Amiga until I touched so called high level programming languages for the first time. For me thinking in strings was really a weird concept. Nowadays I find it extremely strange to think of bits and bytes when being confronted with strings.
- 6y ago
- swlkr 6y agoI'm partial to https://github.com/antirez/sds https://github.com/antirez/sds these days
- mariusor 6y agoThe only problem I have with antirez's lib is that he didn't make it into a single header library.
- spacechild1 6y agoIs it so hard to add a single source file to your build system? If yes, then you can do #include “sds.c“ in some random source file. In fact, that's what so-called header-only libraries in C implicitly do. shudder
- mariusor 6y agoA C file implies a compilation unit. For the projects I write I like to have a single compilation unit per binary (what's called a unity build). In the case of C, this doesn't bring much speed to the table, but it allows for a simpler build toolchain none the less.
- macjohnmcc 6y agostrcpy is a coding challenge where I work for interviews. I typically ask them to write it as the standard version and ask them why they might not want to use it to see if they are aware of the risks. After that I ask them to modify the code to be buffer safe. And for those claiming C++ knowledge ask them to make it work for wchar_t as well to see if they can write a template. Some people really struggle with this.
- mxcrossb 6y agoWhat I don’t understand is why C programmers use the built in strings. It’s like rolling your own sorting algorithm every time you need it. Surely someone could write a better string library in C that hides the complexity. The real problem is that C programmers are apparently allergic to using other people’s code.
- TimTheTinker 6y ago... except for libc, which apparently which is hardly ever questioned.
- cycloptic 6y agoThere are a large number of those libraries. Every large C project eventually seems to grow its own string class.
- user5994461 6y ago>>> Surely someone could write a better string library in C that hides the complexity. In short, it's not possible to write a nice string library in C because C simply doesn't support objects, and by extension doesn't support libraries. Strings are a perfect example of an "object" in what is later known as object oriented programming. C doesn't have objects, it's the last mainstream language that's simply not object oriented at all, and that prevents from making things like a nice string library. If you're curious, the closest thing you will see in the C world is something like GTK, a massive C library including string and much more (it's more known as a GUI toolkit but there are many lower level building blocks). It's an absolute nightmare to use because everything is some abuse of void pointers and structs.
- zlynx 6y agoI think you mean GLib as seen here https://developer.gnome.org/glib/stable/glib-Strings.html https://developer.gnome.org/glib/stable/glib-Strings.html GLib and GTK are closely aligned parts of GNOME so they are easy to get mixed up.
- 6y ago
- brmgb 6y agoThe issue is pretending that C even has strings as a semantic concept. It just doesn't. C has sugar to obtain a contiguous block of memory storing a set number of bytes and to initialize them with values you can understand as the string you want. Then you are passing a memory address around and hoping the magic value byte is where it should be. C is semantically so poor, I find it hard to understand why people use it for new projects today. C++ is over complicated but at least you can find a good subset of it.
- anikom15 6y agoC is a good language for solo projects because of it’s simplicity. By simplicity, I mean understanding what it’s doing under the hood. ‘Portable assembly’ is not an unfitting title. Big C projects work well when they are carefully maintained (like Git).
- akira2501 6y ago> but at least you can find a good subset of it. It's a constantly shifting subset, though. Moving slowly is a feature of C for some.
- account42 6y agoYou can pick a subset of C++ now and it will not be worse in the future. There may be better ways to do things added but that seems a weird thing to complain about and you can just ignore it if you want.
- rbanffy 6y ago> strings in C are actually hard, Strings in C are more like a lie. You get a pointer to a character and the hope there is a null somewhere before you hit a memory protection wall. Or a buffer for something completely unrelated to your string. And that's with ASCII, where a character fits inside a byte. Don't even think about UTF-8 or any other variable-length character representation. In fairness, the moment you realize ASCII strings are a tiny subset of what a string can be, you also understand why strings are actually very complicated.
- sellyme 6y ago> In fairness, the moment you realize ASCII strings are a tiny subset of what a string can be, you also understand why strings are actually very complicated. Oh absolutely, but it's a pretty reasonable expectation that any contemporary language should handle that complexity for you. The entire job of a language is to make the fundamental concepts easier to work with.
- jstanley 6y agoC very much does make the fundamental concepts easier to work with, it merely disagrees with you about exactly which concepts are fundamental :).
- rbanffy 6y agoSadly, strings are, at the same time, complicated enough to be left outside the fundamental concepts of a language, but far too useful to be left outside the fundamental concepts of a realistically viable language.
- loeg 6y agoTruncation, even if it is wrong in an application logic sense, is strictly superior to UB (and in practice, buffer overruns, which can be exploitable). That's the main benefit of strlcpy/strlcat. It is certainly possible to construct a security bug due through truncation! But it is much more common to have security bugs from uncontrolled buffer overruns.
- lenkite 6y agoIf only C had followed the Pascal way to have the size with a string - so much human suffering could have been avoided!
- throw0101a 6y agoIt was considered: > C designer Dennis Ritchie chose to follow the convention of null-termination, already established in BCPL, to avoid the limitation on the length of a string and because maintaining the count seemed, in his experience, less convenient than using a terminator.[1][2] * https://en.wikipedia.org/wiki/Null-terminated_string#History https://en.wikipedia.org/wiki/Null-terminated_string#History Richie et al had experience with the B language: > In BCPL, the first packed byte contains the number of characters in the string; in B, there is no count and strings are terminated by a special character, which B spelled *e. This change was made partially to avoid the limitation on the length of a string caused by holding the count in an 8- or 9-bit slot, and partly because maintaining the count seemed, in our experience, less convenient than using a terminator. * https://www.bell-labs.com/usr/dmr/www/chist.html https://www.bell-labs.com/usr/dmr/www/chist.html
- WalterBright 6y agoWhenever I review C code, I first look at the string function uses. Almost always I'll find a bug. It's usually an off by one error dealing with the terminating 0. It's also always a tangled bit of code, and slow due to repeatedly running strlen. But strings in BASIC are so simple. They just work. I decided when designing D that it wouldn't be good unless string handling was as easy as in BASIC.
- mjcohen 6y agoIIRC, in the early days of the Commodore PET, it used a method of keeping track of strings that was fine in an 8k machine but was too slow in a 32k machine. They had to make a change that avoided quadratic time on the larger machine. So string handling in BASIC wasn't always that simple.
- 867-5309 6y ago+1 for the PET mention since it was my first "computer". much overlooked in favour of the 64
- tlavoie 6y agoAh, yes. I recall the luxury of a Commodore of my very own (a C128), after using PETs in school. We had a whole three of them at the time, with a shared, dual-floppy drive for the set. Naturally, our teacher wisely pushed hard on figuring what you could out on paper first.
- dotancohen 6y ago> Naturally, our teacher wisely pushed hard on figuring what > you could out on paper first. Specifically in the case of the Commodores (I grew up on a C128) I find this observation backwards. Sure, if you only had three machines for twenty students then time on the machine was valuable. But on those machines there was so much to explore with poke (and peek to know what to put back). From changing the colours of the display to changing the behaviour of the interpreter. I think that I discovered the for loop at eight years old just to poke faster!
- ryandrake 6y ago> ~~Technically~~ Optionally, C11 has strcpy_s and strcat_s which fail explicitly on truncation. So if C11 is acceptable for you, that might be the a reasonable option, provided you always handle the failure case. One of the big problems with C programmers is they often neglect to check for and handle those failure cases. Did you know that printf() can fail, and has a return value that you can check for error? (Not you, personally, but the "HN reader" you) Do you check for this error in your code? Many of the string functions will return special values on error, but I frequently see code that never checks. Unfortunately, there isn't a great way to audit your code for ignored return values with the compiler, as far as I know. GCC has -Wunused-result, but it only outputs a warning if the offending function is attributed with "warn_unused_result". I'm not a huge fan of using return values for error checking, but we have the C library that we have.