[HN Gopher] C Strings and my slow descent to madness
       ___________________________________________________________________
        
       C Strings and my slow descent to madness
        
       Author : Decabytes
       Score  : 101 points
       Date   : 2023-04-06 12:22 UTC (10 hours ago)
        
 (HTM) web link (www.deusinmachina.net)
 (TXT) w3m dump (www.deusinmachina.net)
        
       | marcodiego wrote:
       | > If we try to print out some Japanese characters... [] The
       | output isn't what we expect.
       | 
       | Yes it is. And I bet on a modern windows version it is too. The
       | terminal has been (probably intentionally) neglected by ms for a
       | long time, but as far as I know this has mostly been fixed on
       | modern windows versions.
       | 
       | EDIT: Author admits it later in the text "will be fixed in
       | Windows 11 and Windows Server 2022"
       | 
       | Also it says "strlen("You riNan u")); [...] and the output is...
       | The length of the string is 12 characters". But according to "man
       | strlen": "RETURN VALUE: The strlen() function returns the number
       | of bytes in the string pointed to by s.". It says nothing about
       | "number of characters".
        
         | nordsieck wrote:
         | > Also it says "strlen("You riNan u")); [...] and the output
         | is... The length of the string is 12 characters". But according
         | to "man strlen": "RETURN VALUE: The strlen() function returns
         | the number of bytes in the string pointed to by s.". It says
         | nothing about "number of characters".
         | 
         | Yeah - when dealing with Unicode, you have to be very clear
         | about whether you're dealing with bytes, runes or glyphs.
        
           | riceart wrote:
           | Runes are not a Unicode concept - that's a Golangism.
           | Basically a code point.
           | 
           | Also in terms of Unicode, graphemes are even more relevant to
           | the programming side than glyphs - unless you're writing a
           | renderer.
        
         | kevingadd wrote:
         | It makes sense to point it out even if it's fixed in win11,
         | lots of people (myself included) are still on 10.
        
         | flohofwoe wrote:
         | > And I bet on a modern windows version it is too.
         | 
         | It's still broken unfortunately, you need to switch the console
         | to a special UTF-8 codepage in your own code:
         | SetConsoleOutputCP(CP_UTF8);
         | 
         | ...and before exit restore it to the original code page.
        
           | st_goliath wrote:
           | ... except that that is also subtly broken.
           | 
           | It works if you write multiple UTF-8 code-units in one go,
           | but breaks if you send them in several writes (and by that, I
           | mean direct writes to the HANDLE). It also breaks if you try
           | to use the ANSI API (with the A suffix), as it internally
           | tries to convert the bytes from codepage-random to UTF-8.
           | 
           | You run into _both_ issues if you try to use the MS
           | implementation of stdio (printf and friends).
           | 
           | And we didn't even discuss command line argument passing yet
           | :-)
           | 
           | I had a lot of fun with this (more explanation in the issue
           | comments): https://github.com/AgentD/squashfs-tools-
           | ng/issues/96#issuec...
           | 
           | I tried to test it with the only other two languages I know
           | besides English: German and Mandarin. Specifically also,
           | because the later _requires_ multi-byte characters to work.
           | Getting Chinese text I /O to work _at all_ in a Windows DOS
           | box, on an existing, German Windows 7 installation was an
           | adventure on it 's own and ended up breaking things in
           | _different_ ways than German text.
           | 
           | Turns out, trying to write language agnostic command line
           | applications on Windows is a PITA.
        
             | flohofwoe wrote:
             | Windows is truly the gift that keeps on giving :D
        
         | jandrese wrote:
         | Honestly if you are expecting a sane and modern text console on
         | Windows you're just begging for disappointment. I note that
         | even the author briefly tries it on Bash and finds it to be
         | undramatic.
        
         | dspillett wrote:
         | _> The terminal has been (probably intentionally) neglected by
         | ms for a long time,_
         | 
         | I don't think it is an intentional lack of care, just a lack of
         | care. Internally MS devs affected by the appalling state of the
         | console just did what the rest of us did and installed an
         | alternative.
         | 
         |  _> but as far as I know this has mostly been fixed on modern
         | windows versions._
         | 
         | Ish. The default console for powershell is better, but a lot of
         | improvements you might be thinking are in there are in fact
         | only in Windows Terminal
         | (https://en.wikipedia.org/wiki/Windows_Terminal) which is not
         | currently included by default.
        
           | cmovq wrote:
           | > you might be thinking are in there are in fact only in
           | Windows Terminal
           | 
           | A lot of those changes are in ConsoleHost, so Windows 10 and
           | 11 get those improvements (like VT100 sequences) in cmd.exe
           | as well
        
       | bruce343434 wrote:
       | Meh. The w_char stuff is barely C's fault. You use wide (constant
       | width) characters then set the terminal encoding to utf8
       | (variable length encoding). What did you expect? It's a windows
       | issue. I can copy paste all sorts of utf8 in "normal" string
       | literals, printf and puts them, and it just works in my terminal.
       | 
       | RE counting characters: this is a whole can of worms. Do you want
       | to count grapheme clusters? Code points? Anything other than just
       | the amount of bytes? Use a unicode library.
       | 
       | The latter part of this article is a bit like those articles that
       | make fun of javascript for having floating point numbers behave
       | like, gasp, floating point numbers.
        
       | _benj wrote:
       | With the woes of string.h being known, why not just use an
       | alternative like https://github.com/antirez/sds ?
       | 
       | I've also been having a blast with C because writing C feels like
       | being a god! But the biggest thing that I like about C is that
       | the world is sort of written on it!
       | 
       | Just yesterday I needed to parse a JSON... found a bunch of
       | libraries that do that and just picked one that I liked the API.
        
         | kerkeslager wrote:
         | > With the woes of string.h being known, why not just use an
         | alternative like https://github.com/antirez/sds ?
         | 
         | That library really doesn't address any of the Unicode issues.
        
         | UncleEntity wrote:
         | Was going to say the same thing.
         | 
         | If you want Unicode in C a wrapper library is pretty much a
         | given.
         | 
         | When I was adding Unicode support to the small scheme
         | interpreter I like playing with I found a super simple string
         | library, a bunch of generated code (who doesn't love 1000 line
         | switch statements) for dealing with utf-8 code points and bob's
         | your uncle. Could have probably found a library that did it all
         | but the goal was learning and yak shaving.
         | 
         | Haven't ever messed with the wide strings, seems like more of a
         | hassle than they're worth.
        
         | kelnos wrote:
         | > _I've also been having a blast with C because writing C feels
         | like being a god!_
         | 
         | That's funny, because I look at it in the opposite way: it
         | makes me feel like a super-fallible human because it's so easy
         | for me to break things in horrible ways, something a
         | hypothetical god would not do.
         | 
         | Something like Rust &str or String would make me feel more like
         | a god, as I can do whatever I want (more or less) without
         | worrying about safety.
        
         | felipemnoa wrote:
         | >>I've also been having a blast with C because writing C feels
         | like being a god
         | 
         | Not trying to be a troll but as someone who has also written a
         | lot of C in the past why do you feel like this?
        
           | _benj wrote:
           | It's the access and control that it gives me!
           | 
           | As when I'd pick Go because I was doing some concurrency, I
           | can now explore a bunch of concurrency libraries, including
           | some implementations that look a lot like Go channels. Want
           | to watch a file for changes? I can do that all the way from
           | taking to the kernel to picking a multi-platform library. I
           | guess, I haven't really found anything that I can't do in C,
           | and if I'm lazy, I can just embed and scripting language to
           | handle things in a higher level. Macros are also very
           | powerful! I've been writing code that writes code for me,
           | export the thing to a .h file and import it using #include.
           | 
           | pkg-config --list-all has become my friend and I keep
           | discovering that the world is written in C and the access to
           | libraries is huge!
           | 
           | Also, idiomatic C is whatever you make it. There are a bunch
           | of ways to skin a cat. Want a different platform? Build tool?
           | Compiler? Debugger? Wanna write you own debugger? C is chill
           | with that.
           | 
           | It's also such a simple language that without much effort you
           | can know everything about it (I don't care that much about
           | anything over C99). I don't know the whole ecosystem, or
           | standard libraries, or data structures and algorithms and
           | whatnot, but the language itself is quite trivial.
           | 
           | With that said, I'm not using C in project teams. In that
           | setting some strong conventions would likely be necessary or
           | even better, something enforced by tools or the compiler
           | (like Go), but yeah, I've been quite enjoying working with C
           | and being kind of annoyed at other langs that I need to use
           | for work because they keep doing all this stuff behind my
           | back that is supposed to help me, but instead is a pain
           | trying to debug and understand what is actually happening
        
             | Georgelemental wrote:
             | Sorry to be "that person", but have you tried Rust yet? It
             | checks a lot of your boxes:
             | 
             | - Access and control, nothing "behind your back"
             | 
             | - Low or high level, as you prefer
             | 
             | - Swap in different implementations (custom allocator,
             | different async runtime, etc)
             | 
             | - Really powerful macros
             | 
             | - Strong conventions, safe by default (but you can break
             | them, go into the weeds if needed)
             | 
             | Downsides compared to your list:
             | 
             | - More complex than C or Go (though less than C++)
             | 
             | - Only one production compiler, and everyone assumes
             | `cargo` build system (though both are very good)
             | 
             | - Library ecosystem not quite as extensive (though there is
             | a lot of good stuff on crates.io, and you can always write
             | bindings to C)
             | 
             | The little things that seal the deal:
             | 
             | - Enums (tagged unions without the danger or boilerplate)
             | 
             | - Zero-cost closures
             | 
             | - Incremental compilation
             | 
             | - "If it compiles, it probably works"
             | 
             | - Standardized documentation via `rustdoc`
             | 
             | - Module system
        
               | _benj wrote:
               | hehe! It's ok to be "that person" :)
               | 
               | Yes, I've tried rust and have even shipped some project
               | with it! I think the things that didn't worked for me was
               | the complexity. It felt like I had to keep a lot of
               | things in mind to be effective, (traits can be a bit
               | obscure, i.e. magic IMHO), also lifetimes and Option made
               | the code complex by either having a bunch of math or
               | .unwrap all over the place.
               | 
               | With that said tho, Rust would be one of my top picks for
               | a professional setting or a codebase that I share with a
               | team, because of the really good defaults that it has! I
               | read once somebody comparing the Rust compiler with a
               | bunch of tiny unit tests that the developer doesn't have
               | to write, and I agree with that!
               | 
               | With C tho, for personal stuff I can do things that I've
               | never do professionally (i.e. use the OS as my GC because
               | when the process dies the OS is gonna "free" my memory
               | allocations anyways! I know, terrible, but I'm having
               | fun! -\\_(tsu)_/-)
        
             | felipemnoa wrote:
             | Thank You! That was a nice explanation.
        
           | t43562 wrote:
           | It's not doing as many things behind your back as the dynamic
           | languages and C++ do. More things are your responsibility.
        
         | t43562 wrote:
         | Yes, I do think that you cannot fairly dismiss C because of
         | strcpy_l only being available on Windows when it's quite
         | possible to implement it yourself or use a library like the one
         | you mention.
        
         | gavinhoward wrote:
         | I encourage people to use sds if that's the best option.
         | 
         | However, I don't think that's the best option if you can roll
         | your own.
         | 
         | I personally think that there should be two different types of
         | strings: static and dynamic ones. The static ones should not be
         | able to be changed, but the dynamic ones can serve as a "string
         | builder" type of sorts.
         | 
         | Second, I don't see sds's first advantage (in the README) to be
         | an advantage. Sure, you may have to explicitly pass the buffer
         | in to C functions, but that tells you that you're calling a
         | function that takes a char array rather than your string. It
         | makes it more explicit.
         | 
         | Second, if you use my method of splitting static strings from
         | dynamic strings, then sds's second advantage doesn't apply
         | because the pointer will never change.
         | 
         | But the disadvantages of sds still apply, and both
         | disadvantages are big since they easily lead to bugs. Hence, I
         | think sds is not the best option if you can make your own.
         | 
         | Oh, another advantage of the static/dynamic split: I can
         | implement small string semantics. For small enough strings, I
         | use a union to put the array into the same bytes as the
         | pointer, so on 64-bit machines, I can have 8-byte strings
         | (including nul) before needing an actual allocation.
        
       | gpderetta wrote:
       | The worst part of C strings is that they tend to show up in APIs
       | (especially system calls). This make interoperability with other
       | languages harder than it should m
        
       | js2 wrote:
       | Programs which have to deal with C strings beyond the bare
       | minimum that libc provides will generally have a set of routines
       | for making it more ergonomic. e.g.:
       | 
       | https://github.com/git/git/blob/master/strbuf.h
        
       | stephc_int13 wrote:
       | If you are using C and do some non-trivial work with strings you
       | should either use a good library to handle strings or build your
       | own.
       | 
       | It is not that difficult in practice.
       | 
       | The old C std lib is, in my opinion, outdated, obsolete and a
       | very bad fit for complex string handling, especially on the
       | memory management side.
       | 
       | In my own framework, the string management module is using a
       | dedicated memory allocator and a "high level" string API with
       | full UTF8 support from the start.
       | 
       | As a general rule, I think that the C std lib is the weakest part
       | of the C language and it should only be used as a fallback.
        
         | 1vuio0pswjnm7 wrote:
         | "If you are using C and do some non-trivial work with strings
         | you should either use a good library to handle strings or build
         | your own."                  unsigned int str_len(const char *s)
         | {          register const char *t;                  t = s;
         | for (;;) {            if (!*t) return t - s; ++t;            if
         | (!*t) return t - s; ++t;            if (!*t) return t - s; ++t;
         | if (!*t) return t - s; ++t;          }        }
         | 
         | I still use this instead of stdlib strlen. Of course I also use
         | software everyday that I know uses stdlib strlen. For most C
         | programs dealing with strings I just use flex and yyleng, which
         | in turn uses the stdlib strlen. Using flex for small jobs is
         | overkill but it's quick and convenient. I am a hobbyist
         | programmer; I write so-called "trivial" programs.
         | 
         | That said, this exact function is used in some "non-trivial"
         | software written by someone else and that person is IMHO a
         | better C programmer than any HN commenter I have seen, most of
         | whom do not let the public see the code they write anyway. Go
         | figure.
         | 
         | NB. I am not the author; this is in the public domain. The
         | author is djb.
        
           | 1vuio0pswjnm7 wrote:
           | Here is the musl stdlib strlen. I do not use glibc.
           | 
           | https://git.musl-
           | libc.org/cgit/musl/plain/src/string/strlen....
           | #include <string.h>        #include <stdint.h>
           | #include <limits.h>                #define ALIGN
           | (sizeof(size_t))        #define ONES ((size_t)-1/UCHAR_MAX)
           | #define HIGHS (ONES * (UCHAR_MAX/2+1))        #define
           | HASZERO(x) ((x)-ONES & ~(x) & HIGHS)                size_t
           | strlen(const char *s)        {        const char *a = s;
           | #ifdef __GNUC__        typedef size_t
           | __attribute__((__may_alias__)) word;        const word *w;
           | for (; (uintptr_t)s % ALIGN; s++) if (!*s) return s-a;
           | for (w = (const void *)s; !HASZERO(*w); w++);        s =
           | (const void *)w;        #endif        for (; *s; s++);
           | return s-a;        }
        
           | stephc_int13 wrote:
           | I think this naming style should be considered obsolete.
           | 
           | - This function will return the number of bytes, not of
           | characters or codepoints. - str and len are both
           | abbreviations, we should use full words when possible - We
           | can also be more explicit about what the function does, it
           | does not simply returns the string length, it counts
           | characters (or bytes in this case)
           | 
           | Here is how I would name it:
           | 
           | u32 CountBytesInString(char* string); u32
           | CountCharactersInString(char* string);
           | 
           | And on the implementation side, this work can be done with
           | SIMD instructions, and be really freaking fast, but still, it
           | should be explicit for the user that the work is O(n)
           | complexity, not exactly free.
        
             | 1vuio0pswjnm7 wrote:
             | "- str and len are both abbreviations, we should use full
             | words when possible -"
             | 
             | u32 and char are abbreviations.
        
               | stephc_int13 wrote:
               | Yes, this is true. And this is a tradeoff, I think that
               | basic types are so widely used that we can use this style
               | of abbreviation without much ambiguity.
               | 
               | strlen is also pretty unambiguous, but I still have to
               | check what strstr means.
        
           | kelnos wrote:
           | Out of curiosity, why do you use this? I expect the builtin
           | strlen() to be even more optimized than this. There's a lot
           | more you can do than a simple loop unrolling.
        
             | YourDadVPN wrote:
             | Especially when there's a decent chance the compiler would
             | replace the loop with a call to strlen.
        
           | asveikau wrote:
           | Don't do this. libc string functions are usually done with
           | hand tweaked assembly or as a builtin by the compiler. They
           | will be faster than this.
           | 
           | This sort of practice is very outdated. The last time it made
           | sense for performance reasons was probably the early 90s or
           | earlier.
           | 
           | Additionally, strlen is not one of the C string functions you
           | want to replace due to defects, the same way you would want
           | to do with crusty old strcpy. If you're working with C
           | strings there is nothing wrong with strlen. (Just don't call
           | it redundantly in a loop body ...)
        
             | stephc_int13 wrote:
             | I've written my own strlen equivalent and benchmarked them
             | against default on different compilers, processors and
             | environments, and they almost always are faster or the same
             | speed.
             | 
             | Default libs are sometimes very optimized but very often
             | they are not, unfortunately.
             | 
             | If you care about performance, you should not rely blindly
             | on the the defaults.
             | 
             | A long time ago I wondered about the performance of memcpy
             | on the Nintendo DS, for sure they would have provided a
             | hand optimized version? And yes, it was handcrafted ARM
             | assembly code, but my own version turned out to be twice as
             | fast.
             | 
             | They simply forgot to use a simple prefetching trick in
             | their implem.
        
         | tyingq wrote:
         | Fair, though there's always the crossover point where you need
         | to interact with the OS, 3rd party libraries, protocols, etc.
         | It's not difficult to miss a spot where your utf8 string gets
         | mangled, truncated, etc.
        
           | stephc_int13 wrote:
           | Yes, do not trust the OS, use the minimal API surface, and
           | build your own toolkit, you won't have to do it often.
        
         | marssaxman wrote:
         | > the C std lib is the weakest part of the C language and it
         | should only be used as a fallback.
         | 
         | I've been musing for a while now: what would it look like if we
         | were to discard the C library and design a new one, leaving the
         | language itself intact?
        
           | scythe wrote:
           | The problem is that it's just too tempting to write something
           | like                   my_function(my_var, 3.6, "bzarflo",
           | my_other_var, false);
           | 
           | The string handling functions are part of the story, but the
           | null-terminated char * is produced when the compiler reaches
           | a string literal, and writing code without being allowed to
           | just use string literals when it's convenient tends to feel
           | like coding with oven mitts on.
        
             | lolcatuser wrote:
             | It's entirely possible to write a wrapper function with a
             | short name to convert string literals to actual string
             | objects.                   my_function(my_var, 3.6,
             | $("bzarflo"), my_other_var, false);
             | 
             | Isn't _that_ much more of a mouthful, and as long as
             | 'my_function' knows to free it, then you're A-OK! The only
             | trouble is '$()' isn't legal in standard C, so a real
             | solution would have to be something like 'str()'.
        
           | athenot wrote:
           | The old MacOS (pre-X) did just that. Strings were all "Pascal
           | strings", ie. with the first byte containing the length of
           | the actual string.
           | 
           | Building blocks for memory were also very different from
           | stdlib, notably the use of Handles, which were pointers of
           | pointers, so that the OS could move a block of data around to
           | defragment the heap behind your back without breaking the
           | memory addressing.
        
             | tialaramex wrote:
             | Pascal strings are _also_ kind of bad though. All sub-
             | string operations need allocation, or have to be defined
             | with intermediate results which aren 't "really" strings,
             | so in that sense it's not an improvement on Zero-terminated
             | strings. Equality tests are cheaper which is nice, since
             | strings of different lengths compare unequal immediately,
             | but most things aren't really improved.
             | 
             | C++ string_view is closer to the Right Thing(tm) - a slice,
             | but C++ doesn't (yet) define anywhere what the encoding is,
             | so... that's not what it could be. Rust's str is a slice
             | _and_ it 's defined as UTF-8 encoded.
        
               | WalterBright wrote:
               | D's strings were defined to be UTF-8 back in 2000.
               | wstring is UTF-16, and dstring is UTF-32.
               | 
               | Back then it wasn't clear which encoding method would
               | turn out to be dominant, so we did all three. (Java was
               | built on UTF-16.)
               | 
               | As it eventually became clear, UTF-8 is da winnah, and
               | the other formats are sideshows. Windows, which uses
               | UTF-16, is handled by converting UTF-8 to -16 just before
               | calling a Windows function, and converting anything
               | coming back to UTF-8.
               | 
               | D doesn't distinguish between a string and a string view.
        
             | WalterBright wrote:
             | > with the first byte containing the length of the actual
             | string
             | 
             | And the wheels fall off with the first string longer than
             | 255 characters.
        
               | mikewarot wrote:
               | Which is why Free Pascal strings are so awesome. I've
               | personally stuffed a billion bytes on one, without
               | issues. They are automatic reference counted, and as
               | close to magic as you can get. You can return one from a
               | function without issue.
               | 
               | However, Free Pascal has the worst documentation of any
               | major project I've ever encountered (The exact opposite
               | of Turbo Pascal), so I can't link to a good reference.
               | Their Wiki is a black hole of nuance and sucks all useful
               | stuff off the internet.
        
               | initplus wrote:
               | You can fix this issue by using a variable width integer
               | encoding for the size.
        
               | burntsushi wrote:
               | It might fix that particular issue, but you still have
               | the same problem that NUL terminated strings have: it's
               | not possible to cheaply create views/slices of a string
               | using the same type.
        
           | klodolph wrote:
           | There are several libraries or projects where people have
           | done exactly that.
           | 
           | You often end up with some kind of structure, or variations
           | of structures, for strings:                   struct string {
           | size_t length;           char data[];         };
           | struct string {           size_t length;           size_t
           | alloc;           char *data;         };
           | 
           | Those are just examples. The tricky part is figuring out the
           | different ownership use cases you want to solve. Because C
           | gives you so much freedom and very little in the standard
           | library, you end up with a lot of variations. You might use
           | reference-counted strings, owned buffers, or string slices,
           | etc. You might want certain types to be distinguished at
           | compile-time and other types to be distinguished at run-time.
           | 
           | An example can be found in the Git source code.
           | 
           | https://github.com/git/git/blob/master/strbuf.h
           | 
           | The history of changes to this file is interesting as well.
           | This is a relatively nice general-purpose string type--you
           | can easily append to it or truncate it.
        
             | stephc_int13 wrote:
             | IMHO that does not solve the main problem, that is
             | individual lifetime management.
             | 
             | I've seen many libs using this style of strings, not
             | convinced by the practicality.
        
               | nickff wrote:
               | What is "individual lifetime management"?
        
               | klodolph wrote:
               | It sounds like you're rephrasing part of my comment back
               | to me, or maybe I'm misinterpreting what you're saying.
               | 
               | If you're not convinced of the practicality, it sounds
               | like you are simply not convinced of the practicality of
               | doing string processing in C at all, which is a fair view
               | point. String processing in C is somewhat a minefield.
               | Libraries like Git's strbuf are very effective relative
               | to other solutions in C, but lack safety relative to
               | other languages.
        
               | stephc_int13 wrote:
               | No, I simply am using a different approach, still in C,
               | where strings are simple char*, null-terminated, nothing
               | hidden with magic fields above the base address of the
               | string.
               | 
               | The trick is to pass an allocator (or container) to
               | string handling functions.
               | 
               | If/when I want to get rid of all the garbage I reset the
               | container/allocator.
        
               | klodolph wrote:
               | Yeah, you should have just said that in the first place.
               | 
               | I've seen similar approaches, e.g. with APR pools, and if
               | your application can work within those restrictions, it's
               | very convenient.
        
           | oneshtein wrote:
           | You can backport Rust standard library to C using
           | https://github.com/eqrion/cbindgen .
        
           | WorldMaker wrote:
           | Glib answer (but also relevant because mentioned in the
           | article, too): it would look a lot like how a lot of people
           | write C++.
        
             | scythe wrote:
             | >Glib answer
             | 
             | A Freudian slip, methinks.
        
               | [deleted]
        
               | WorldMaker wrote:
               | How so? First definition I find of glib is "(of words or
               | the person speaking them) fluent and voluble but
               | insincere and shallow", which _is_ mostly what I meant
               | about that answer. There was some sincerity in my answer,
               | but certainly somewhere in the border space of irony and
               | sarcasm, which many people do take as insincerity.
        
               | bingo3131 wrote:
               | https://en.wikipedia.org/wiki/GLib
        
           | epr wrote:
           | The creator of this library (antirez) is a regular here on
           | hn.
           | 
           | I believe this is used by Redis.
           | 
           | https://github.com/antirez/sds
        
           | stephc_int13 wrote:
           | I think it could be very nice.
           | 
           | C is not perfect, there are some parts of the syntax that I
           | strongly dislike, like casting or function pointers
           | declaration...
           | 
           | But it is overall a good enough syntax, much simpler than
           | C++.
        
             | marssaxman wrote:
             | Amending the syntax is fun but rapidly becomes a slippery
             | slope; soon enough you find yourself designing a new
             | successor language, as has been done many times before.
             | Simply scrapping the mostly-unhelpful C stdlib and
             | inventing new, modern abstractions for allocation, IO,
             | text, threading, etc seems like a more tractable problem.
        
               | stephc_int13 wrote:
               | I fully agree.
        
               | int_19h wrote:
               | It has the same fundamental problem, though: you have to
               | rewrite most existing code, which hinders adoption. In
               | this case, it might actually hinder it _more_ than also
               | improving the language itself, since people would be more
               | willing to take that leap if there are more benefits to
               | be had from it.
        
         | maxloh wrote:
         | Is your framework open sourced?
        
           | stephc_int13 wrote:
           | Not 100% decided yet, but it is very probable that I will
           | open source it.
        
         | m463 wrote:
         | > The old C std lib is, in my opinion, outdated, obsolete
         | 
         | ...and has been since most of us ever used C.
         | 
         | I think one of the major failings of C was the lack of a good
         | standard library that updated with the times.
         | 
         | Actually, I believe a rich standard toolbox was one of the best
         | features of python, and helped with its success.
        
           | pjmlp wrote:
           | Which is why most applications ping back into POSIX when
           | available, not that fixes the security issues with the
           | standard library.
        
         | attractivechaos wrote:
         | > _especially on the memory management side._
         | 
         | Libc string functions don't manage memory. They can be used no
         | matter where your strings are stored. It is more of a choice
         | between generality vs convenience in common cases.
        
           | nicoburns wrote:
           | A lot of them require the string to be null terminated rather
           | than taking a length.
        
           | stephc_int13 wrote:
           | I think this is the main culprit of the libc string
           | functions, you _have_ to provide buffers to store results,
           | and the responsibility of managing those individually can be
           | annoying, and bug prone, resulting in vulnerabilities.
           | 
           | Passing an allocator (like Zig) or a container (like in my
           | framework) to anything that needs to allocate some memory to
           | store a result is both explicit, low overhead and quite
           | convenient in practice.
        
           | skitter wrote:
           | You cannot e.g. store a string as a slice of another string
           | (unless the slice reaches the end).
        
       | jmclnx wrote:
       | Yes this is something to get use to. The BSDs created strlcpy(3)
       | and wcslcpy(3)
       | 
       | https://man.openbsd.org/strlcpy.3
       | 
       | https://man.openbsd.org/wcslcpy.3
       | 
       | which to me will help with some of these issues. Too bad other
       | Operating Systems do not have these. On Linux there is libbsd to
       | get these, but I would like to see these to be added to the stdc.
       | 
       | Instead the c23 standard is messing with realloc(3) which could
       | break some old programs. I have not looked at that in detail yet,
       | so maybe it is a non-issue :)
        
         | hgs3 wrote:
         | Yup, there is also Linux's strscpy which doesn't require
         | reading memory from the source string beyond the specified
         | "count" bytes and the return value is idiot proof.
        
         | [deleted]
        
         | torstenvl wrote:
         | I don't know of a compiler that forces you to use the newest
         | version of the standard, which is why I've always kind of
         | thought "don't break old code" was treated too much like dogma.
         | So from that perspective, a non issue.
         | 
         | However, there is a problem that has nothing to do with old
         | code: they increased the number of situations that constitute
         | undefined behavior, with no public discussion and no
         | justification. It's frankly dangerous behavior.
        
         | anthomtb wrote:
         | strlcpy is nice due to the guaranteed NUL termination.
         | 
         | strlcpy is not so nice due to the strange (IMO) return value of
         | the _number of characters in the source string_. Which could be
         | the number of characters copied or much, much larger than the
         | number of characters copied. snprintf does the same thing.
         | 
         | So using strlcpy is safe (by C's low bar) but using the return
         | value may be highly unsafe.
        
           | jandrese wrote:
           | The thing that annoys me the most about strlcpy is that it is
           | supposed to be safer, but what happens in the case where the
           | source string is not properly NULL terminated? You might
           | think that it will stop at the character limit you specified,
           | but that's not what it does. It just blows on past the end of
           | the buffer looking for a \0 until it either finds one or
           | causes a segmentation violation.
           | 
           | IMHO I would like it much more if the return values were:
           | 0: string copied       1: string partially copied but
           | truncated       -1: Error, errno set.  This can occur when
           | src or dst are NULL.
        
             | hgs3 wrote:
             | Linux's strscpy addresses these issues.
        
               | jandrese wrote:
               | Which is great if you're a kernel developer, a bit of a
               | moot point for application developers.
        
         | alecco wrote:
         | Ushering out strlcpy() https://lwn.net/Articles/905777/
        
         | GabrielTFS wrote:
         | These functions are in the current POSIX draft - though not
         | published, it's quite unlikely to be removed (someone actually
         | specifically filed an issue against POSIX to try and get it
         | removed, basically on the basis of "it's not perfect so it
         | should be removed", and the issue got rejected on the basis
         | that there's no consensus for removal, and it seems unlikely
         | this will change), and as a result, the functions are getting
         | added to glibc: https://sourceware.org/pipermail/libc-
         | alpha/2023-April/14696...
        
       | flohofwoe wrote:
       | This is from a C fan: If you are going to do any string heavy
       | work, please use anything else than C (Python is pretty nice for
       | this sort of stuff for instance).
       | 
       | And if you need to use C anyway, then please use anything else
       | than the string functions from the standard library. The C stdlib
       | is (mostly) a leftover from the K&R era when opinions about what
       | makes a good API were very different from today, and C was a much
       | 'harsher' language.
       | 
       | C is pretty nice for a lot of things, but working with strings
       | definitely isn't one of them.
        
         | cozzyd wrote:
         | I would say, unless there's a performance reason not to, always
         | use asprintf for every string operation.
        
       | analog31 wrote:
       | As a cellist, I was about to sympathize when I read the title.
        
       | djha-skin wrote:
       | Related and good read about strcpy in the kernel:
       | https://lwn.net/Articles/905777
        
       | userbinator wrote:
       | Well-written C tends to minimise string usage in general,
       | preferring to convert to another format as soon as possible.
       | Allocating, copying, and passing around strings in large
       | quantities is not a good idea for efficiency, but of course some
       | people coming from other HLLs seem to try to do it anyway, which
       | causes many other problems.
        
         | agumonkey wrote:
         | to the point I often wonder if strings should exist.. buffers
         | -> symbols | structs.
        
         | bell-cot wrote:
         | THIS.
         | 
         | And programming, engineering, and life in general have so, SO
         | many other situations where "X is not very good at doing Y".
         | Yet (my experience) guys seem extremely resistant to the
         | common-sense strategy of "then try to minimize how much Y you
         | do with X".
        
       | nathell wrote:
       | > Our last function is strcmp. It looks at two strings and
       | determines whether they are equal to each other or not. If they
       | are it returns 0. If they aren't it returns 1.
       | 
       | No it doesn't.                   RETURN VALUES              The
       | strcmp() and strncmp() functions return an integer greater than,
       | equal              to, or less than 0, according as the string s1
       | is greater than, equal to,              or less than the string
       | s2.  The comparison is done using unsigned
       | characters, so that '\200' is greater than '\0'.
        
         | andoma wrote:
         | Reminds me of the incorrect cast of memcmp() return value that
         | resulted in this bug: https://bugs.mysql.com/bug.php?id=64884
        
         | cassepipe wrote:
         | I don't have MacOs to prove it but I believe `strcmp` on MacOs
         | returns either 0, 1 or -1
        
           | skywal_l wrote:
           | https://developer.apple.com/library/archive/documentation/Sy.
           | ..
        
             | cpeterso wrote:
             | strcmp's return value is loosely defined because some
             | implementations return the difference between the
             | characters in the string to avoid some conditional checks
             | or jumps. Something like:                 int d = a[i] -
             | b[i];       if (d == 0) return d;
        
         | Decabytes wrote:
         | I've added a footnote to my incorrect explanation and credited
         | you. I'm still a C noob so thank you for pointing this out!
        
       | bobajeff wrote:
       | I've been wondering lately why many people write c in c++ rather
       | than just c. I think this might be the reason.
        
         | qsort wrote:
         | People write C in C++ because they don't actually know C++ and
         | think it's "basically C with classes and strings".
         | 
         | There are legitimate reasons why someone would rather write C,
         | but "I don't understand RAII" is not one of them.
        
           | owlbite wrote:
           | C with basic templates is normally what I want. Occasionally
           | other C++-isms drift in, but normally to the harm of the code
           | quality.
        
           | wruza wrote:
           | C++ doesn't require you to commit to all of its features
           | and/or paradigms. Using it as you see fit is valid. Just
           | don't advertise yourself as a C++ programmer to the job
           | market, as it's not what most people expect.
           | 
           | There's nothing wrong with "C with classes and strings" idea
           | by itself, if that is your choice or a consciously sufficient
           | level of competence.
        
             | qsort wrote:
             | It doesn't require you to commit to all of its features,
             | that's certainly correct. But it does require you to commit
             | to its _principles_ ; if you're needlessly passing naked
             | pointers around, you're really writing C code with a C++
             | compiler.
        
               | wruza wrote:
               | I don't see how it requires you to commit to any
               | principles, if you can avoid those you don't need and
               | still successfully compile. That's called "suggests" or
               | "allows", not "requires". Yes, some people are writing C
               | code with classes and strings in C++. That's why we call
               | this mode "C with classes and strings". I believe that
               | you are attached to these principles (see their
               | benefits), and that is fine. But not everybody likes
               | full-on C++.
        
         | zabzonk wrote:
         | the usual accusation is that many people write c++ code as if
         | it were c. also, the the code in 2nd ed of K&R was all compiled
         | with stroustrup's c++ compiler, as there wasn't a c compiler
         | that could handle it.
        
         | tjoff wrote:
         | For a long time on windows the official MS recommendations was
         | to use the c++ compiler for c projects.
         | 
         | Which was exerbated by an obsolete c compiler that only
         | supported c89.
        
       | infradig wrote:
       | I stopped when I read strcmp returns 0 if two strings are equal
       | and 1 if they aren't.
        
         | jstimpfle wrote:
         | cmp stands for compare, so the behaviour (returns <0, 0, or >0)
         | is completely reasonable. With three possible outcomes, the
         | function is suitable to be used for sorting.
        
         | tom_ wrote:
         | A much better description of strcmp's behaviour:
         | https://en.cppreference.com/w/c/string/byte/strcmp
        
         | t-3 wrote:
         | It's actually 0 if equal, positive if greater, negative if less
         | than.                 > The strcmp() and strncmp() functions
         | return an integer greater than, equal to, or less than 0,
         | according to whether the string s1 is greater than, equal to,
         | or less than the string s2.  The comparison is done using
         | unsigned characters, so that '\200' is greater than '\0'.
        
       | simonblack wrote:
       | "We're not in Kansas any more, Toto"
       | 
       | Or to paraphrase that "We're not in Python any more, and C is not
       | Python".
       | 
       | You know what sends _me_ insane? Indentation and lack of fixed
       | types in Python. But I _don 't_ have problems with C strings.
       | Because I have grown to love and know C's string foibles just
       | like the author will certainly not be driven insane by 'Python's
       | shortcomings according to me'.
       | 
       | The world is full of people who complain that something or other
       | is different from what they know, so that 'other' is wrong.
       | That's just being isolationist. Everything has its own
       | advantages, its own disadvantages. Let's accept that and move on,
       | instead of making mountains out of mole-hills.
        
         | Sohcahtoa82 wrote:
         | > Indentation and lack of fixed types in Python.
         | 
         | Whenever I see someone complain about Python's indentation, my
         | brain internally translates it to "I poorly format my code."
         | 
         | If you code is properly formatted, then Python's indentation is
         | never a problem. I _praise_ Python 's indentation-as-syntax
         | because it prevents issues like a dangling else or a forgotten
         | brace while also making proper formatting a _requirement_ for
         | your program to run.
        
       | gavinhoward wrote:
       | Okay, I agree that _by default_ , C strings are bad.
       | 
       | But it doesn't have to stay that way. Someone else in the
       | comments mentioned antirez's sds library for dynamic strings.
       | This works, but you could also easily roll your own. All you need
       | is an init function, and perhaps an assert or other check at the
       | end of it that the string has a nul terminator.
       | 
       | At that point, type checking will let you blindly pass those
       | strings (or their char arrays) to any of those C functions
       | without worry.
       | 
       | Edit: I'll also add that I think a string library should have a
       | difference between static strings and string builders (dynamic
       | strings). It makes everything easier.
        
         | magicalhippo wrote:
         | > _by default_ , C strings are bad.
         | 
         | C strings aren't bad. They can't be, because they don't exist.
         | C doesn't have strings. And _that_ is the issue.
         | 
         | As you say, things get a lot better when you actually introduce
         | strings as a concrete concept rather than a set of lose
         | conventions.
        
           | tialaramex wrote:
           | I don't think it's useful to pretend C doesn't have strings
           | when it has string literals.
           | 
           |  _WUFFS_ doesn 't have strings. That's what a language which
           | doesn't have strings looks like, you can't write "Hello,
           | world" in WUFFS because it involves Strings, which WUFFS
           | doesn't have, and I/O, which WUFFS also doesn't have.
           | 
           | A pretence that C doesn't have strings because it lacks a
           | concrete string type in the language itself also seems like
           | you'd be claiming C++ doesn't have strings, Zig doesn't have
           | strings, and Rust came _pretty close_ to not having strings
           | (for a while it was mooted to make Rust 's str just a slice
           | [u8] but today Rust does bless str as a distinct type even
           | though e.g. &str and &[u8] aren't very different)
        
             | magicalhippo wrote:
             | C++ before C++11 didn't. I've fixed errors in projects
             | which was due to string literals not being std::string.
             | After C++11 things are more murky due to user literals[1].
             | I'd lean towards saying the language still doesn't, but
             | yeah, murky.
             | 
             | Zig and Rust I don't know enough about.
             | 
             | And I'm not pretending. C has string literals which are of
             | a _non-distinct type_. You can 't distinguish between a
             | string literal and an array of characters. This is the
             | crucial bit.
             | 
             | The result is that the standard library, and lots of other
             | code, relies on convention alone to pass strings around.
             | This has been and continues to be the source for countless
             | serious bugs. The kind of bugs which are a total non-issue
             | in languages which has strings.
             | 
             | [1]:
             | https://en.cppreference.com/w/cpp/language/user_literal
        
           | flohofwoe wrote:
           | > C doesn't have strings.
           | 
           | C has _string literals_ though, and those bake a specific
           | string representation into the language (of course libraries
           | can use their own string representation, but those then need
           | at least some conversion function from string literals).
        
           | junon wrote:
           | There is no "loose convention". A C string is a null
           | terminated string of non-null bytes. That's the definition.
           | Working with them in memory-unconstrained environments is
           | unnecessarily hard.
        
             | magicalhippo wrote:
             | There is indeed just convention. The language defines
             | _string constants_ similar to what you say[1] (an array of
             | characters, terminated by a null character), but in the
             | language itself there 's no way to declare that a function
             | takes a string rather than a pointer to a character.
             | Alternatively if you work with a fixed-sized character
             | array, there's nothing separating it from "just" an array
             | of characters that are _not_ null terminated.
             | 
             | So that strcmp expects a string rather than a pointer to a
             | character is just convention. In languages which actually
             | has strings as a concrete concept, like say Pascal and
             | derivatives, you can actually differentiate between those
             | two cases.
             | 
             | [1]: https://www.gnu.org/software/gnu-c-manual/gnu-c-
             | manual.html#...
        
               | int_19h wrote:
               | That means that the strings aren't properly reflected in
               | the type system. But the existence of string literals
               | with a very definite in-memory layout means that it's not
               | just a convention even so.
        
               | magicalhippo wrote:
               | But you can't use those string literals in any way
               | _without_ relying on convention.
        
               | int_19h wrote:
               | That was my point. Although you can, actually - since
               | literals themselves are array-typed, you can sizeof them
               | to get the character count without relying on null
               | termination. It's even possible to get a non-null-
               | terminated literal if the target array type is not large
               | enough to fit null, e.g.:                  char s[3] =
               | "foo"; // not null-terminated!
        
               | giantrobot wrote:
               | > character count
               | 
               | Byte count.
        
               | int_19h wrote:
               | If you want to be pedantic, "an object declared as type
               | char is large enough to store any member of the basic
               | execution character set". It doesn't actually have to be
               | a byte.
               | 
               | In practice, in C context, character == char == byte.
               | Other concepts have to use different names to avoid
               | confusion with the language spec.
        
         | junon wrote:
         | There's also RapidString, though the original author seems to
         | have disappeared off of the internet. Would be curious to see
         | benchmarks against sds.
        
         | bluetomcat wrote:
         | The best way to write C is to treat strings as memory locations
         | with characters and nothing more. Every such memory location
         | has an allocated size, either statically at compile-time (with
         | literals and arrays), or dynamically with malloc and friends.
         | Treat string operations as mere memory operations and don't
         | imagine them to be something else. The str* functions from the
         | standard library are just convenience helpers which are not
         | adding "string" functionality whatsoever.
        
           | nerdponx wrote:
           | Maybe you could summarize this by saying that C strings are
           | "strings of bytes", not "strings of characters".
        
       | pixelbeat__ wrote:
       | String handling in C has many gotchas indeed. Here are some of my
       | notes on the subtleties:
       | 
       | https://www.pixelbeat.org/programming/gcc/string_buffers.htm...
        
       | teddyh wrote:
       | > _strcmp takes two strings and returns 0 when they are true._
       | 
       | ITYM "equal", not "true".
        
       | FpUser wrote:
       | C strings are bad for sure. Consider those raw assembly. Instead
       | of using it directly get some decent string library ASAP and use
       | it exclusively.
        
         | jstimpfle wrote:
         | They are just arrays, like everything else in the language. If
         | you don't want to manage plain arrays, better look for a
         | different language.
        
           | throwawaymaths wrote:
           | they are not "just" arrays, they are arrays with a null-
           | terminated expectation. Most of the time. The lack of
           | consistency and difficulty communicating the expectations is
           | what is hair-pulling in C, and that's on top of the
           | difficulty of communicating the difference between an array
           | and a pointer in C.
        
           | FpUser wrote:
           | You are free to make good use of strings as arrays. I've
           | written tons of code including firmware for MCU so I think
           | I'll keep to my own practices.
        
       | zh3 wrote:
       | I once got called in to fix an SS7 stack suffering from poor
       | performance. Pretty well written, and not obvious at first sight
       | why it was going slow. Most of it was low-level bit fiddling, and
       | some small strncpy's() - generally about 8 chars or so.
       | 
       | Didn't take that long to profile (well, printf's as no profiling
       | available) and figure out it was the strncpy's causing the
       | problem, but why? Well, there was a handy 8 megabyte buffer used
       | for working memory that the strings were being copied into that
       | for modification.
       | 
       | From the strncpy() man page:-
       | 
       | >If the length of src is less than n, strncpy() pads the
       | remainder of dest with null bytes.
       | 
       | Ah. So every little strncpy was essentially copying the string
       | then zeroing out 7,999,992 bytes. And there were lots of little
       | strncpy's...
        
         | PaulDavisThe1st wrote:
         | The appearance of strncpy() in any source code is an immediate
         | panic attack for me. It should never be used, and if it is
         | used, it should be removed.
         | 
         | Similar rule for sprintf(), all instances of which should be
         | replaced by snprintf().
        
           | bluGill wrote:
           | Unfortunately there often isn't a better replacement in your
           | standard library (embedded systems are weird). I ended up
           | using strncpy followed by automatically setting the last byte
           | of the string to null.
        
             | PaulDavisThe1st wrote:
             | strncpy() in particular is so bad that you're better off
             | (for a rare exception to the rule) just writing your own,
             | that does what most people think strncpy() does (or should
             | do) rather than what it actually does.
        
       | benmmurphy wrote:
       | `strlcpy` is the function you probably want. but again it is not
       | standard. https://lwn.net/Articles/507319/
       | 
       | I think the reason people don't want to standardise this kind of
       | function is it often gives wrong behaviour. for example if you
       | are trying to copy a string into a fixed buffer and its too long
       | then often it is an error or potentially even a security bug to
       | truncate it. so these functions generally do the 'wrong' thing
       | even though they are 'safer'. if you are dealing with static
       | buffers then I think you should be explicitly checking the source
       | fits in the target and then handling the error case. you could
       | even have a function like `strlcpy` that does `strlen` then
       | checks if it fits, then does the copy or return an error code.
       | alternatively, if the string should always fit and you don't want
       | to handle the error case then the safe thing to do is check at
       | runtime that it fits then abort the program if it doesn't fit.
        
         | Night_Thastus wrote:
         | strlcpy is not needed, strcpy_s (not strncpy_s) is safe and is
         | part of the C11 standard.|
         | 
         | In fact, strlcpy is _worse_ :
         | 
         | * strlcpy truncates the source string to fit in the destination
         | (which is a security risk)
         | 
         | * strlcpy does not perform all the runtime checks that strcpy_s
         | does
         | 
         | * strlcpy does not make failures obvious by setting the
         | destination to a null string or calling a handler if the call
         | fails.
        
           | zabzonk wrote:
           | > strcpy_s is part of the C11 standard
           | 
           | an optional part, which makes it pretty worthless, if it were
           | not so already.
        
         | kelnos wrote:
         | On systems that aren't memory constrained, we just shouldn't be
         | using static buffers at all. Just always use something like
         | asprintf() and free() the result when you're done. No, it's not
         | in the C or POSIX standards, and that's a shame, but it's at
         | least available on Linux and the BSDs.
         | 
         | I end up working on a lot of code that uses Glib, so I tend to
         | use g_strdup_printf() a lot, which works the same as
         | asprintf().
         | 
         | Ultimately the cost of allocations is usually not a big deal,
         | and you gain a lot of safety. Sure, you then have to remember
         | to free(), but I'll take a memory leak over a segfault (and its
         | possible security consequences) any day.
         | 
         | And if allocation cost _is_ a problem, you can always go back
         | and optimize with static buffers later. That shouldn 't be the
         | default that people reach for, though.
        
       | tragomaskhalos wrote:
       | K&R contains this beautiful koan-like string copy code:
       | while (*t++ = *s++)             ;
       | 
       | Honestly the elegance of this thing was one of the hooks that
       | made me fall in love with C. But this was from a now-forgotten
       | age of innocence, as there are so many "nopes" around this line-
       | and-a-half that one would, rightly, be tarred and feathered for
       | ever putting it in a program today.
        
       | kevin_thibedeau wrote:
       | wchar_t is a massive landmine that should never be used since its
       | size varies by platform. The locale of the compiler has to match
       | the end user for L prefixed strings to work correctly. Likewise
       | char16_t and char32_t are just swimming against the easy path at
       | this point. You're much better off sticking to UTF-8 and using
       | the C11 u8 prefix on literals so you can use the regular string
       | API and never have to worry about locale settings.
        
         | Decabytes wrote:
         | This is great advice! I wasn't aware of this and I will keep
         | that in mind. When I first came across Unicode literals I was
         | unsure when exactly you would use them over wchar_t
        
       | Tarragon wrote:
       | > So how can we handle this case safely? There are a few ways I
       | can think of.
       | 
       | strdup:
       | 
       | > The strdup() function returns a pointer to a new string which
       | is a duplicate of the string s. Memory for the new string is
       | obtained with malloc(3), and can be freed with free(3).
        
       | Dwedit wrote:
       | > "But for real if anyone knows how to get this to work on
       | Windows 10 let me know!"
       | 
       | Since the May 2019 update, Windows 10 has supported declaring the
       | code page in a manifest file.
       | 
       | In Visual Studio, you must add "/utf-8" to the compiler command
       | line, this makes it parse the source code as a UTF-8 file, and
       | makes it output UTF-8 string literals.
       | 
       | To make console output work, call the Win32 function
       | "SetConsoleOutputCP(65001);"
       | 
       | To get support for opening files with names that aren't in your
       | system codepage:
       | 
       | * Create a manifest file as shown in
       | https://learn.microsoft.com/en-us/windows/apps/design
       | /globalizing/use-utf8-code-page
       | 
       | * Add this as an "Additional Manifest File" in Visual Studio
       | project settings for the manifest tool
       | 
       | Additionally, there is an undocumented NTDLL function
       | "RtlInitNlsTables" that sets the code page for the process. It is
       | difficult to use without a lot of example code, but some app
       | locale type tools (used to change locale for a process) make use
       | of this function.
        
       | kloch wrote:
       | > At first this looks great, but there is a problem. What happens
       | when the source string minus the null terminator is as long as
       | the size of the destination string? The answer is that the
       | destination gets filled with all the characters of the source
       | string with no room left for the null terminator.
       | 
       | The 'n' in strncpy is mainly there to help you avoid overrunning
       | the destination, it does not guarantee whatever makes in there is
       | null-terminated.
       | 
       | This is why you should always explicitly set the last byte to
       | zero after using strncpy (and never _ever_ use strcpy).
       | char dest[16];            strncpy(dest, src, 15);
       | dest[15]=0;
        
       | mahoho wrote:
       | Just a pedantic comment, but You riNan u is arigatou or roughly
       | "thanks", not "hello". Hello would usually be konnichiha or, more
       | confusingly, Jin Ri ha
        
         | commandlinefan wrote:
         | Sort of unfortunate, because there's really no good translation
         | for "hello" into Japanese - you'd say konnichiwa in the
         | morning, in the afternoon konbanwa and moshimoshi when
         | answering the phone...
        
           | scrame wrote:
           | you'd say ohayogozaimasu in the morning, konnichiwa in the
           | afternoon and konbanwa in the evening.
           | 
           | I don't know how to type japanese on my phone, but the first
           | is literally "early" surrounded by honorifics. The characters
           | for konnichiwa means "this/now", "day" and the "wa" at the
           | end is an article making the previous phrase the subject of
           | the sentence. Same with konbanwa, but for evening instead of
           | day.
           | 
           | no idea on the etymology of moshimoshi for answering the
           | phone, though.
        
         | teddyh wrote:
         | moshimoshi
        
       | photochemsyn wrote:
       | For initial string input, i.e. from a network/file/terminal
       | stream, using fgetc and/or fgets plus code to verify and sanitize
       | makes the most sense IMO.
       | 
       | This does mean you have to write a lot of C code for what would
       | be simple tasks in other languages, e.g. a correct file open,
       | read-to-dynamically-allocated-memory, and file close with good
       | error checking is a full page (at least) of dense code in C and
       | just two lines in Python.
       | 
       | If you've done a good job sanitizing and verifying all the input
       | to your program, only then does it becomes relatively safe to use
       | the standard string functions, with caveats for multithreading.
       | 
       | Asking ChatGPT to compare and contrast fgetc and fgets is a good
       | place to start, and then ask how to use fgets to handle errors
       | during stream I/O, and what can go wrong with multithreading etc.
       | Then take a look at the sqlite source code for in-house C-string
       | handling, here's the take-away comment:
       | 
       | "Because there is no consistency, we will define our own."
       | 
       | https://github.com/sqlite/sqlite/blob/master/src/util.c
        
       | russellbeattie wrote:
       | Literally 25 years ago I was a beginner programmer and tried
       | writing a .dll for Microsoft's Internet Information Server, which
       | was relatively new at the time. (I hadn't so much as seen a Unix-
       | based OS at the time, let alone understood CGI). C strings were
       | mind boggling and frustrated me so much I simply gave up. Happily
       | around the same time, MS introduced Active Server Pages and I was
       | able to use that and never messed with C again. It's amazing the
       | same issues still exist decades later.
        
       | bluetomcat wrote:
       | In well-written C, you don't work with strings the way you do in
       | other HLLs. For example, extracting and copying substrings is
       | something unnecessary, unless you want to modify the parent
       | string. Otherwise, a substring is represented by a pointer and a
       | size_t length, and can easily be printed that way via the "%.*s"
       | printf specifier:                   const char *s = "Hello
       | World!";         const char *world = s + 6;         size_t
       | world_len = 5;         printf("%.*s\n", world_len, world);
        
         | gpderetta wrote:
         | On other HLLs it is easy to have subviews on other strings. C
         | makes is needlessly hard by requiring null termination in half
         | the APIs.
        
         | tom_ wrote:
         | * consumes an int, not a size_t:
         | https://port70.net/~nsz/c/c11/n1570.html#7.21.6.1p5
        
           | TremendousJudge wrote:
           | I love how in every C code snippet on every comment on this
           | thread, somebody got something wrong. I take it as a sign
           | that it's probably best to avoid C as much as possible.
        
             | giantrobot wrote:
             | ! multithreaded it's not Hey, at least code
        
             | avar wrote:
             | Any C compiler that isn't a trivial toy implementation will
             | warn about that, so it's hardly a C gotcha.
        
           | lolcatuser wrote:
           | Maybe my least favorite "feature" of C. I can manage most
           | aspects of zero-terminated strings well enough, but when I
           | have to specify the length of them, is it an 'int', 'size_t',
           | 'ssize_t', or something else? (Answer: All of the above!)
        
       | habibur wrote:
       | I don't use null terminated strings. ptr+len struct everywhere.
       | And when I need to call an API, like fopen, I make a temporary
       | copy of that string + the null termination, do my work and then
       | free it.
       | 
       | You can printf non-null terminated strings too. Check
       | printf("%.*s", length, strptr).
        
         | t43562 wrote:
         | A long time ago my solution was ptr+len but I allocated 1 more
         | byte so that if a string had to be given to libc, I could
         | terminate it at that time. No need for a copy then.
        
           | wvenable wrote:
           | BASIC strings in Windows -- you store the length in the 4
           | bytes before the pointer to the string and put a null
           | terminator at the end.
           | 
           | https://learn.microsoft.com/en-us/previous-
           | versions/windows/...
        
         | torstenvl wrote:
         | > _You can printf non-null terminated strings too. Check
         | printf( "%._s", length, strptr).*
         | 
         | I haven't checked yet, but I'm about 90% confident that's UB.
         | Is printf() guaranteed not to read to the end of the string
         | when you give it a length?
        
           | habibur wrote:
           | Won't read a single byte beyond the length you give it.
           | 
           | And this is a standard practice in libraries for printing
           | pre-determined lengthed string.
        
           | nwellnhof wrote:
           | Yes, it is. From the C11 spec:
           | 
           | > Characters from the array are written up to (but not
           | including) the terminating null character. If the precision
           | is specified, no more than that many bytes are written. If
           | the precision is not specified or is greater than the size of
           | the array, the array shall contain a null character.
        
       | draw_down wrote:
       | [dead]
        
       | Night_Thastus wrote:
       | It's important to mention that strncpy (and also strncpy_s) are
       | really not a strcpy replacement, it's not intended for the same
       | usages. The name is a total misnomer. Do not use strncpy that
       | way!
       | 
       | In any case, strcpy_s (which is a good replacement for strcpy) is
       | part of the C11 standard. I'm confused how that isn't considered
       | portable.
        
         | zabzonk wrote:
         | it's an _optional_ part of the standard, and so can't be relied
         | on. also, the idea behind it is pretty poor.
        
           | Night_Thastus wrote:
           | Don't GCC, Clang _and_ MSVC provide it? It may be optional in
           | practice but if the major compilers support it, it 's not
           | really an issue.
           | 
           | The idea may not be perfect, but for C which is intended to
           | be low overhead, strcpy_s is about as good as it gets. If you
           | want something more user friendly, that is what C++ is for
           | with std::string, or library implementations like Boost or QT
           | string.
        
             | zabzonk wrote:
             | MSVC supports it, as it is an MS invention, designed by
             | some intern, i guess. other compilers may or may not, by
             | switches/#defines. it is worthless in any case.
        
       | jeffrallen wrote:
       | Programming in Go made me a better C programmer, because now I no
       | longer use C strings, only a buffer/length/capacity struct.
        
       | coldpie wrote:
       | It's unfortunate the author put the arrays-are-pointers thing so
       | early in the doc, as that's a very beginner-to-C mixup and really
       | nothing at all to do with strings. Otherwise, yep. It's pretty
       | bad. C is a great language, but its string handling is definitely
       | garbage. You get used to it pretty quick, and it's not hard to
       | write a handful of sane wrappers or a simple string library for
       | your own use, but the standard library's terrible string
       | functions are an unending source of bugs.
        
         | gpderetta wrote:
         | I don't see any mention or insinuations of arrays-are-pointers
         | anywhere in the article. Am I missing something?
        
           | coldpie wrote:
           | This bit:                   But you might be asking. "Why
           | can't I just assign the source variable directly to the
           | destination variable?"              int main() {
           | char source[] = "Hello, world!";           char* destination
           | = source;                    strcpy(destination, source); //
           | Copy the source string to the destination string
           | printf("Source: %s\n", source);
           | printf("Destination: %s\n", destination);
           | return 0;         }         You can. It's just that
           | destination now becomes a char* and exists as a pointer to
           | the source character array. If that isn't what you want them
           | this will almost certainly cause issues.
        
         | unwind wrote:
         | This is almost a cliche among many C language lawyers and/or
         | Stack Overflow answer-rich people and I know you mean well,
         | but: arrays are not pointers.
         | 
         | In some contexts, the name of an array decays to a pointer to
         | its first element. That is a better way of putting it, and it's
         | a (much) weaker statement.
         | 
         | Edit: if they were the same, this code:                   int
         | foo[] = {1, 2, 3};         int *bar = foo;         printf("%zu
         | and %zu\n", sizeof foo, sizeof bar);
         | 
         | Would print the same valde twice, but it doesn't. On Ideone [1]
         | I got 12 and 8.
         | 
         | [1]: https://ideone.com/CP7WTu
        
           | int_19h wrote:
           | This also makes a big difference once we start talking about
           | pointers to arrays.                   int a[] = {1, 2, 3}
           | int (*p1)[3] = &a; // ok         int (*p2)[3] = &a[0]; // not
           | ok         int *p3 = &a; // not ok
           | 
           | (It should be noted that these will compile with warnings in
           | C due to implicit conversions via void*, but you're still
           | risking UB if you actually use the resulting value. They are
           | all errors in C++ because it doesn't have implicit conversion
           | from void*.)
        
             | strkitten wrote:
             | Nothing wrong with your third line. Did you mean something
             | else?
        
               | int_19h wrote:
               | I forgot the &; comment updated now, and I added another
               | example.
        
               | strkitten wrote:
               | Got it, thanks!
        
       ___________________________________________________________________
       (page generated 2023-04-06 23:01 UTC)