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