[HN Gopher] The C23 edition of Modern C
       ___________________________________________________________________
        
       The C23 edition of Modern C
        
       Author : bwidlar
       Score  : 492 points
       Date   : 2024-10-15 16:06 UTC (1 days ago)
        
 (HTM) web link (gustedt.wordpress.com)
 (TXT) w3m dump (gustedt.wordpress.com)
        
       | belter wrote:
       | Important reminder just in the Preface :-)
       | 
       | Takeaway #1: "C and C++ are different: don't mix them, and don't
       | mix them up"
        
         | pjmlp wrote:
         | Specially relevant to all those folks that insist on "Coding C
         | with a C++ compiler", instead of safer language constructs, and
         | standard library alternatives provided by C++ during the last
         | decades.
        
           | Spivak wrote:
           | I mean as long as your goal is specifically to do that I
           | think it's fine. Using a C++ compiler to compile a C program
           | isn't that rare.
        
           | com2kid wrote:
           | Perfectly valid to do if you need to interface with a large C
           | code base and you just want to do some simple OO here and
           | there. Especially if you cannot have runtime exceptions and
           | the like.
           | 
           | This is how I managed to sneak C++ into an embedded C
           | codebase. We even created some templates for data structures
           | that supported static allocation at compile time.
        
             | f1shy wrote:
             | What would be an example of "simple OO here and there" that
             | cannot be done cleanly in plain C?
        
               | raluk wrote:
               | RAII
        
               | f1shy wrote:
               | Is RAII Object orientation? I thought it was an idiom of
               | C++ by Stroustrup.
        
               | runevault wrote:
               | It doesn't necessarily have to be OO no. Rust uses RAII
               | and it uses traits instead of traditional OO style
               | inheritance etc. You do need something like
               | destructors/drop trait for it to work as far as I know
               | though.
        
               | tjoff wrote:
               | The killer feature of RAII is when combined with
               | exceptions. But sneaking in exceptions in an embedded C
               | project isn't something I'd encourage or recommend.
               | 
               | C++ imo doesn't offer anything compelling for the
               | embedded usecase. Especially not considering all the
               | footguns and politics it brings.
               | 
               | You can of course be strict and diligent about it but if
               | you are you are pretty much just writing C anyway. Better
               | to do it explicitly.
               | 
               | Allowing the use of the C++ standard library has been one
               | of my biggest regrets (not that it was my decision to
               | make, I fought it).
        
               | AlotOfReading wrote:
               | C++ offers lots of compelling things for embedded use
               | cases, like enum classes (finally fixed in C23),
               | constexpr, std::optional, namespaces, and
               | atomics/generics that are much smaller dumpster fires.
               | 
               | There's an effort to extract the good parts and make it
               | work for embedded use cases or even bring them into C.
               | Khalil Estelle on WG21 has been working on an
               | experimental, deterministic runtime for exception
               | handling, to give one example. Constexpr is an example of
               | the latter that's now showing up in C23.
        
               | tjoff wrote:
               | I don't disagree, but these are in the ~2% convenience at
               | most. With the huge baggage of including C++ in a
               | project. The cost of learning C++ easily outweighs all
               | those benefits. If you happen to have a proficient C++
               | team (that actually know embedded), go for it!
        
               | AlotOfReading wrote:
               | Speaking more broadly than just the std implementation,
               | but result types like optional shouldn't be a 2%
               | convenience, they should be used in most function calls
               | that return errors. Rust is the obvious example here.
        
               | tjoff wrote:
               | If you argue for Rust I'm all for it, arguably much less
               | of a learning curve than C++ too.
        
               | Ragnarork wrote:
               | Strong disagree on that one. Even though C++ has a lot of
               | features that take a while to learn, getting started with
               | C++ is simpler by miles than getting started with Rust.
               | 
               | One exception to that in my experience: dependencies,
               | although I think that's a bit deceiving as yes, it's
               | easier to get dependencies in Rust but in some areas
               | they're way less mature and can sometimes be a pain to
               | work with (usually when dealing with big C or C++
               | libraries that have been quickly glued to a Rust
               | interface to be available in Rust).
        
               | tjoff wrote:
               | Agree, writing bad C++ is easy. But being competent in
               | C++ requires much more than being competent in rust.
               | 
               | And not being competent in C++ is not great. You are
               | going to be much more productive in C. The feedback loop
               | is much faster, that is, feedback from your designs.
               | 
               | Contrast with Rust which is harder to get going but
               | doesn't require nearly as much to be decent.
        
               | kccqzy wrote:
               | There are a lot of large C++ shops that purposefully
               | disable exceptions and yet still use RAII usefully. It's
               | so useful that in many C codebases you see people using
               | RAII. For example Gtk has g_autoptr and g_autofree.
               | 
               | One of the most compelling things C++ offers to embedded
               | use case is moving runtime initialization to compile-time
               | initialization by liberally using constexpr functions.
               | You literally ask the compiler to do work that would
               | otherwise be done at runtime.
        
               | tjoff wrote:
               | RAII is useful without exceptions yes. I guess it is the
               | other way around. Exceptions are not useful without RAII
               | (sorry not sorry most garbage collected languages ;)).
               | 
               | But without exceptions it is mostly syntactic sugar
               | anyway.
               | 
               | If compile time initialization is the most compelling
               | usecase I'll rest my case. Good feature, yes! Hardly
               | worth switching language for.
        
               | adamrezich wrote:
               | Namespaces, methods.
        
               | f1shy wrote:
               | Namespaces is not object orientation, is it? Am I missing
               | something? You can place functions (methods) inside of
               | structs in C23, can't you?
        
               | staunton wrote:
               | On a high level, "object orientation" means you think of
               | your code as representing the state and interactions of
               | objects. You can equally well do this in assembly. If you
               | think of some namespace as a "singleton object" then
               | that's what it is.
               | 
               | I guess what you're really asking is what are the best or
               | most common ways to do OO in C?
        
               | f1shy wrote:
               | Oh. I learned that object orientation is primarily a way
               | to structure data and code, such that the data is
               | encapsulated with the code that works on it, in so called
               | objects. So an Object is the Data, plus the functions
               | that work on the data, an ensure that some invariants are
               | kept. In OO parlance, that code gets executed by sending
               | messages (calling methods).
               | 
               | Where can I find something about objects being "think of
               | your code as representing the state and interactions of
               | objects" honesty totally new to me.
               | 
               | So no, certainly I'm not asking ways to do OO in C. But
               | it seems to be more definitions of object orientation as
               | I thought...
        
               | int_19h wrote:
               | There's no clear definition of what OO is, so the best
               | you can do pragmatically is look at mainstream languages
               | that are broadly recognized as OO and try to deduce the
               | commonalities.
               | 
               | If you do that, you'll notice that, for example,
               | encapsulation is _not_ a part of that de facto
               | definition, because languages like Python and (until
               | recently) JavaScript lack it, despite being considered
               | OO.
               | 
               | Indeed, the only two things that appear to be
               | consistently present in all OO languages are: 1) some
               | notion of object identity as distinct from object state,
               | and 2) runtime polymorphic dispatch.
        
               | epcoa wrote:
               | > Where can I find something about objects being "think
               | of your code as representing the state and interactions
               | of objects" honesty totally new to me.
               | 
               | I'm scratching my head how you think this is materially
               | different than what you described in your first para.
               | s/state/data and s/interactions/methods.
               | 
               | If anything though I would say the GP is more aligned
               | with the classic definition as it highlights the focus is
               | more on the messages (interactions) themselves rather
               | than the implementation.
        
               | adamrezich wrote:
               | Correct, and you did ask specifically for OO things, but
               | I thought I'd list namespaces too as far as "C++ things
               | you might use when writing C-like C++ code".
               | 
               | Another big one that I always forget C still doesn't
               | support is function overloading.
        
               | uecker wrote:
               | Function overloading is a feature that makes code less
               | self-documenting without providing any meaningful value.
               | Operator overloading is more interesting, because you can
               | build you domain language with nice syntax. But I also
               | tend to think that this is not really worth it.
        
               | adamrezich wrote:
               | In C++ where you have methods? Sure. It would be nice to
               | have in C, though. But, alas, ABI compatibility.
        
               | listeria wrote:
               | Function overloading enables the building of powerful
               | idioms like swap, operator== and this [1] proposal for
               | composable hashing to name a few. And when combined with
               | templates (and ADL to provide some encapsulation with
               | namespaces) you can build some interesting abstractions
               | akin to the io module in go, like this implementation of
               | io.ReadFull():                   template<typename R>
               | constexpr ssize_t         io::ReadFull(R reader, char
               | *buf, size_t len)         {           ssize_t recvd = 0,
               | ret;                do {             ret = read(reader,
               | &buf[recvd], len - recvd);             if (ret > 0)
               | recvd += ret;             else               break;
               | } while (recvd < len);                return recvd;
               | }
               | 
               | ---
               | 
               | 1: https://open-
               | std.org/jtc1/sc22/wg21/docs/papers/2014/n3980.h...
        
               | int_19h wrote:
               | You can handcode vtables in C, just as you can handcode
               | loops in assembly (i.e. it works but it's verbose, not
               | particularly readable, and brings more footguns).
               | 
               | But why would you do that if you have an instrument that
               | lets you work at the same level as C, but with methods
               | provided as a proper abstraction that maps exactly to
               | what you'd have written yourself anyway?
        
               | uecker wrote:
               | I don't know, I never found the "proper abstraction" be
               | more than irrelevant syntactic sugar. And the cost of C++
               | is that you end up putting everything in the header (IMHO
               | the biggest design flaw of the language) and then compile
               | time start to get long....
        
               | bobmcnamara wrote:
               | Templating on pixel classes so that a blitter builds all
               | supported pixel paths separately and inlines them.
               | 
               | Yes you can do it less cleanly with macros or inline
               | functions. But you can't do it performantly with struct
               | and function pointers.
        
               | cozzyd wrote:
               | CRTP?
        
               | com2kid wrote:
               | You can do anything in C that you want to. Of course one
               | can make v-tables and all of that, and even do
               | inheritance.
               | 
               | But having the "class" keyword is nice. Having built in
               | support for member functions is nice.
               | 
               | Sometimes a person just wants the simplicity of C++ 2003.
               | 
               | (In reality I was working on a project where our compiler
               | only supported C++ 2003 and we had a UI library written
               | in C++ 2003 and honestly pure C UI libraries kind of suck
               | compared to just sprinkling in a bit of C++ sugar.)
        
               | throwaway2037 wrote:
               | > You can do anything in C that you want to.
               | 
               | How about destructors?
        
               | com2kid wrote:
               | You can obviously build any runtime system you desire in
               | C, including one that parses and executes C code with
               | additional features added in. The wisdom of doing this is
               | questionable.
               | 
               | Though I've actually seen macro systems that do things
               | akin to destructors, although less automatically.
        
             | pjmlp wrote:
             | Yeah, but one should provide C++ type safe abstractions on
             | top.
             | 
             | Just like one doesn't use Typescript to keep writing plain
             | old JavaScript, then why bother.
        
           | flohofwoe wrote:
           | Funny because for a long time the Microsoft MSVC team
           | explicitly recommended compiling C code with a C++ compiler
           | because they couldn't be arsed to update their C frontend for
           | over two decades (which thankfully has changed now) ;)
           | 
           | https://herbsutter.com/2012/05/03/reader-qa-what-about-vc-
           | an...
        
             | rdtsc wrote:
             | That thing always baffled me, this huge company building a
             | professional IDE couldn't figure out how to ship updates to
             | the C compiler.
             | 
             | > it is hard to say no to you, and I'm sorry to say it. But
             | we have to choose a focus, and our focus is to implement
             | (the standard) and innovate (with extensions like everyone
             | but which we also contribute for potential standardization)
             | in C++.
             | 
             | I mean, yeah if it came from a two member team at a
             | startup, sure focus on C++, understandably. But Microsoft,
             | what happened to "Developers! Developers! Developers!"?
        
               | Jtsummers wrote:
               | It's not baffling, it's remarkably consistent. They
               | implemented Java as J++ and made their version
               | incompatible in various ways with the standard so it was
               | harder to port your code away from J++ (and later J#).
               | They implemented things in the CSS spec almost exactly
               | opposite the specification to lock people into IE (the
               | dominant browser, if you have to make your site work with
               | 2+ incompatible systems which will you focus on?). Not
               | supporting C effectively with their tools pushed
               | developers towards their C++ implementation, creating
               | more lock-in opportunities.
        
               | AlotOfReading wrote:
               | Funnily enough, the intellisense parser _does_ support C
               | syntax because it 's using a commercial frontend by
               | edison under the hood. MSVC's frontend doesn't.
        
               | pjmlp wrote:
               | It was on purpose, Microsoft was done with C, the
               | official message was to move on to C++.
               | 
               | The change of heart was the new management, and the whole
               | Microsoft <3 FOSS.
        
               | rdtsc wrote:
               | > It was on purpose, Microsoft was done with C
               | 
               | Indeed, and yet here we are with C23
               | 
               | > The change of heart was the new management, and the
               | whole Microsoft <3 FOSS.
               | 
               | Yeah, agree. To me the turning point was when they
               | created WSL.
        
               | kragen wrote:
               | Microsoft didn't create C23 and they don't <3 FOSS.
               | They're accepting that they have to deal with FOSS, but
               | installing Windows will still make your Linux system
               | unbootable until you fix it with a rescue disk, among
               | numerous other unfriendly things they do.
        
               | alexvitkov wrote:
               | I haven't seen Windows fuck up the EFI partition or
               | delete the other entries in a while now. After installing
               | it the machine will usually boot directly into it, but it
               | should be just a toggle in the firmware to switch back to
               | GRUB.
        
               | kragen wrote:
               | That's an improvement! When did they fix that?
        
               | chithanh wrote:
               | > installing Windows will still make your Linux system
               | unbootable until you fix it with a rescue disk
               | 
               | This is no longer true. On UEFI systems the only thing
               | you have to do normally is fix the boot order. In fact
               | installing Linux first and Windows second tends to be the
               | better dual-boot strategy nowadays.
               | 
               | Fixing the boot order can be done from UEFI setup, and
               | even from Windows command line                 bcdedit
               | /enum firmware       bcdedit /set {fwbootmgr}
               | displayorder {yourlinuxuuid} /addfirst
               | 
               | (Put single quotes around {} if you use PowerShell
               | instead of CMD.exe)
        
               | kragen wrote:
               | Thank you for the correction and the howto! How long have
               | I been wrong about this?
        
               | pjmlp wrote:
               | Microsoft doesn't take part on WG14, and MSVC only does
               | up to C17 nowadays.
        
             | pjmlp wrote:
             | Yeah, 12 years ago, when governments couldn't care less
             | about nation state cyberattacks, and Microsoft was yet to
             | be called by the Congress to testify on their failures.
        
         | jpcfl wrote:
         | Bjarne should have called it ++C.
        
           | card_zero wrote:
           | Because people choose to use pre-increment by default instead
           | of post-increment?
           | 
           | Why is that?
        
             | jejdjdbd wrote:
             | Why would you use post increment by default? The semantics
             | are very particular.
             | 
             | Only on very rare occasions I need post increment
             | semantics.
             | 
             | And in those cases I prefer to use a temporary to make the
             | intent more clear
        
               | card_zero wrote:
               | People seem to mostly write a typical for loop ending
               | with ; ++i){
               | 
               | But I write ; i++){ and seeing it the other way round
               | throws me off for a minute, because I think, as you put
               | it, why would you use those very particular semantics?
               | 
               | But I guess this is only a semantic argument.
        
               | johannes1234321 wrote:
               | > why would you use those very particular semantics?
               | 
               | The difference is that i++ has to keep a copy to the
               | original around as the return value is the pre-increment
               | value, while with ++i that isn't needed as the resulting
               | value is being returned.
               | 
               | In the for loop that shouldn't matter as a) for an
               | integer it is essentially for free (it is just reordering
               | when the relevant register is set) and b) that value is
               | hopefully optimized out anyways by the compiler, however
               | as there are cases where it matters some people prefer
               | the ++i style, some just think it looks better.
        
               | layer8 wrote:
               | In C++ the semantics can differ, in that copying an
               | object for post-increment might require a memory
               | allocation internally (for example in the case of a
               | BigInt class), which may fail and throw an exception. For
               | consistency, using pre-increment by default and unless
               | you _really_ need post-increment, is a good habit.
        
               | Quekid5 wrote:
               | I've seen either style, but it the argument about which
               | is proper is pointless. Any modern compiler will optimize
               | either equally well, unless you're doing something that
               | actually depends on the order of the increment.
        
               | kanbankaren wrote:
               | No, for fundamental datatypes pre/post-increment doesn't
               | matter, but for classes that overload those operators,
               | the postfix form creates a temporary object hence people
               | write
               | 
               | for(auto it = begin(v); it != end(v); ++it)
        
               | secondcoming wrote:
               | It makes no difference if the increment is done on an
               | int, but it can make a different if your `i` is some
               | object with its own ++ operator.
        
               | codr7 wrote:
               | If you're used to the idiom, the intent couldn't be
               | clearer.
               | 
               | I miss it when switching between C/++ and other
               | languages.
        
               | flohofwoe wrote:
               | I rarely use pre-increment tbh, but post-increment all
               | the time for array indices (since typically the array
               | should be indexed with the value before the increment
               | happens).
               | 
               | If the pre- or post-increment behaviour isn't actually
               | needed, I prefer `x += 1` though.
        
             | tialaramex wrote:
             | Why use this operator? Like most C and C++ features the
             | main reason tends to be showing off, you learned a thing
             | (in this case that there are four extra operators here) and
             | so you show off by using it even if it doesn't make the
             | software easier to understand.
             | 
             | This is not one of those beginner -> journeyman -> expert
             | cycles where coincidentally the way you wrote it as a
             | beginner is identical to how an expert writes it but for a
             | very different reason. I'd expect experts are very
             | comfortable writing either { x = k; k += 1; } or { k += 1;
             | x = k; } depending on which they meant and don't feel an
             | itch to re-write these as { x = k++; } and { x = ++k; }
             | respectively.
             | 
             | I'm slightly surprised none of the joke languages add
             | equally frivolous operators. a%% to set a to the remainder
             | after dividing a by 10, or b** to set b as two to the power
             | b or some other silliness.
        
               | cozzyd wrote:
               | It's more useful for pointers than for values, IMO
        
               | layer8 wrote:
               | The idiomatic                   void strcpy(char *s, char
               | *t)         {             while (*s++ = *t++)
               | ;         }
               | 
               | (straight from K&R) wouldn't work without it.
        
               | n_plus_1_acc wrote:
               | Which many people find unreadable compared to other
               | versions.
        
               | beeforpork wrote:
               | And for several reasons.                 * is it (*s)++
               | or *(s++)?       * it is not *++s nor ++*s
               | 
               | And I have seen                 *(*s)++
               | 
               | in some places!
               | 
               | It is concise syntax but very confusing.
        
               | chongli wrote:
               | K&R actually teaches this as a desirable idiom? People
               | should not be recommending K&R to beginners today!
        
               | trealira wrote:
               | Kernighan spends a page whittling strcpy down to just
               | that, with various intermediate versions. After showing
               | you that version, he describes it like this:
               | 
               |  _Although this may seem cryptic at first sight, the
               | notational convenience is considerable, and the idiom
               | should be mastered, because you will see it frequently in
               | C programs._
        
               | trealira wrote:
               | They can be useful when adding things to an array in a
               | loop. A trivial example which removes a character from a
               | null terminated string:                 void
               | remove_char(char *s, char c) {         size_t i, j;
               | for (i = j = 0; s[i] != '\0'; i++)           if (s[i] !=
               | c)             s[j++] = c;         s[j] = '\0';       }
               | 
               | This might be better expressed with a higher order filter
               | function, but C is too low level for things like that.
               | 
               | There are also idioms for stack manipulation using them:
               | "stack[sp++] = pushed" and "popped = stack[--sp]".
               | 
               | C code does a lot of incrementing and decrementing by
               | one, and so having dedicated syntax for it is convenient.
        
               | jancsika wrote:
               | Note that in your example there appear to be three
               | distinct meanings:
               | 
               | 1. prefix incr/decr precedence: "stack[--sp]"
               | 
               | 2. postfix incr/decr precedence: "s[j++]"
               | 
               | 3. i have no particular preference for the precedence and
               | am just using a shorthand I inherited from my ancestors
               | whose use cases are no longer relevant to me: "i++" in
               | your for loop
               | 
               | My rank speculation is that C programmers get in a habit
               | of #3 and then forget to consider precedence in an
               | expression where it matters.
               | 
               | In any case, it would be interesting to do a scan of
               | github to see how often prefix and suffix incr/decr had
               | to get switched up in a bugfix patch.
        
             | int_19h wrote:
             | It should be ++C because with C++ the value you get from
             | the expression is the old one.
             | 
             | If you're asking why people use pre-increment by default
             | instead of post-increment, it's mostly historical. The
             | early C compilers on resource-constrained platforms such as
             | early DOS were not good at optimization; on those, pre-
             | increment would be reliably translated to a simple ADD or
             | INC, whereas code for post-increment might generate an
             | extra copy even if it wasn't actually used.
             | 
             | For C++ this was even worse with iterators, because now it
             | depended on the compiler's ability to inline its
             | implementation of postfix ++, and then prove that all the
             | copies produced by that implementation have no side effects
             | to optimize it to the same degree as prefix ++ could.
             | Depending on the type of the underlying value, this may not
             | even be possible in general.
             | 
             | The other reason is that all other unary operators in C are
             | prefix rather than postfix, and mixing unary prefix with
             | unary postfix in a single expression produces code that is
             | easy to misunderstand. E.g. *p++ is *(p++), not (*p)++,
             | even though the latter feels more natural, reading it left-
             | to-right as usual. OTOH *++p vs ++*p is unambiguous.
        
               | card_zero wrote:
               | K&R seems to use pre-increment early on, then post-
               | increment consistently (or a lot, anyway, I haven't done
               | a thorough check) after chapter 3, in situations where
               | either would do. In fact, after introducing post-
               | increment at 2.8.
        
               | jpcfl wrote:
               | > It should be ++C because with C++ the value you get
               | from the expression is the old one.
               | 
               | You get it!
        
             | wpollock wrote:
             | The PDP-11 that C originally targeted had address modes to
             | support the stack. Pre-increment and post-decrement
             | therefore did not require a separate instruction; they were
             | free. After the PDP-11 went the way of the dodo, both forms
             | took a machine cycle so it (mostly) became a stylistic
             | issue. (The two operators have different semantics, but the
             | trend to avoid side-effects in expressions means that both
             | are most often used in a single expression statement like
             | "++x;" or "x++;", so it comes down to your preferred
             | style.)
        
               | zabzonk wrote:
               | Please explain what you mean by "a separate instruction".
        
               | spc476 wrote:
               | Some idiomatic C code to copy a string (I'm not saying
               | this is good C code, but it's just an example):
               | while(*d++ = *s++)           ;
               | 
               | On the Motorola 68000 (based somewhat on the PDP-11) the
               | code would look like:                   loop:
               | move.b  (a0)+,d0                     move.b  d0,(a1)+
               | bne     loop
               | 
               | while on the x86 line, it would be:
               | loop:       mov     al,[rsi]                     mov
               | [rdi],al                     inc     rsi     ; extra
               | instruction!                     inc     rdi     ; extra
               | instruction!                     cmp     al,0
               | jne     loop
               | 
               | Yes, there are better ways to write that code for both
               | the 68K and x86, but I hope this gets the point across.
        
               | wang_li wrote:
               | > loop: move.b (a0)+,d0 move.b d0,(a1)+
               | 
               | ...
               | 
               | > loop: mov al,[rsi] mov [rdi],al
               | 
               | This hurts my brain. When we invent time machines I'm
               | going to use it to go back and slap whoever at intel came
               | up with that operand order.
        
               | wpollock wrote:
               | A MOV and an INC, as opposed to just the MOV.
        
           | wnoise wrote:
           | Nah. It's just the natural semantics -- he added stuff to C,
           | but returned something that wasn't actually more advanced...
        
         | f1shy wrote:
         | A couple of months ago, in the company I work, there was a talk
         | from HR, where they explained how to make a good CV (the
         | company is firing lots of people). She say: "if you have
         | experience in programming C, you can writing just that, or, if
         | you have lots of experience in C, is customary to write ``C++
         | Experience'' "
         | 
         | Sooo... yeah... I should definitely change company!
        
           | kstrauser wrote:
           | That literally made me do a spit take, and it was fizzy water
           | and it burned.
           | 
           | My god. That's amazing.
        
           | thenipper wrote:
           | How many pluses until you should just say you have D
           | experience?
        
             | varjag wrote:
             | Possibly three. Four pluses is naturally C#.
        
         | jasode wrote:
         | _> Takeaway #1: "C and C++ are different: don't mix them, and
         | don't mix them up"_
         | 
         | Where "mixing C/C++" is helpful:
         | 
         | - I "mix C in with my C++" projects because "sqlite3.c" and
         | ffmpeg source code is written C. _C++ was designed to
         | interoperate with C code._ C++ code can seamlessly add
         | _#include "sqlite3.h"_ unchanged.
         | 
         | - For my own code, I take advantage of "C++ being _mostly_ a
         | superset of C" such as using old-style C printf in C++ instead
         | of newer C++ cout.
         | 
         | Where the _" C is a totally different language from C++"_
         | perspective is helpful:
         | 
         | - knowing that compilers can compile code in "C" or "C++" mode
         | which has ramifications for name mangling which leads to "LINK
         | unresolved symbol" errors.
         | 
         | - knowing that C99 C23 has many exceptions to "C++ is a
         | superset of C" :
         | https://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B...
        
           | accelbred wrote:
           | C++ can seamlessly include C89 headers.
           | 
           | The C library headers for libraries I write often include
           | C11/C99 stuff that is invalid in C++.
           | 
           | Even when they are in C89, they are often incorrect to
           | include without the include being in an `extern "C"`.
        
             | nuancebydefault wrote:
             | Extern "C" around the prototypes is mandatory, otherwise
             | your linker will search for C++ symbols, which cannot be
             | found in the C libraries you pass it.
        
             | Conscat wrote:
             | Clang supports C11 - 23 in C++, as well as some future C
             | features like fixed-point integers. The main pain points
             | with Clang are just the fundamental differences like void*
             | and char, which don't typically matter much at an
             | interoperability layer.
        
               | flohofwoe wrote:
               | There's a lot of subtle differences between 'proper' C
               | and the C subset of C++, since C++ uses C++ semantics
               | everywhere, even for its C subset.
               | 
               | Many C++ coders are oblivious to those differences
               | (myself included before I switched from 'mainly C++' to
               | 'mainly C') because they think that the C subset of C++
               | is compatible with 'proper' C, but any C code that
               | compiles both in a C++ and C compiler is actually also a
               | (heavily outdated) subset of the C language (so for a C
               | coder it takes extra effort to write C++ compatible C
               | code, and it's not great because it's a throwback to the
               | mid-90s, C++ compatible C is potentially less safe and
               | harder to maintain).
               | 
               | For instance in C++ it's illegal to take the address of
               | an 'adhoc-constructed' function argument, like:
               | sum(&(bla_t){ .a = 1, .b = 2, .c = 3, .d = 4 });
               | 
               | (godbolt: https://www.godbolt.org/z/r7r5rPc6K)
               | 
               | Interestingly, Objective-C leaves its C subset alone, so
               | it is always automatically compatible with the latest C
               | features without requiring a new 'ObjC standard'.
        
               | pjmlp wrote:
               | Because Objective-C initial proposal is that everything
               | that isn't touched by Smalltalk like code, clearly in
               | brackets or @annotarions, is plain C.
               | 
               | The pre-processor original compiler, before the GCC fork,
               | would leave everything else alone, blindly copying into
               | the generated C file.
        
             | kccqzy wrote:
             | Yeah plenty of headers first have `#ifdef __cplusplus` and
             | then they add `extern "C"`. And of course even then they
             | have to avoid doing things unacceptable in C++ such as
             | using "new" as the name of a variable.
             | 
             | It takes a little bit of an effort to make a header work on
             | C and C++. A lot less effort than making a single Python
             | file work with Python 2 and 3.
        
               | flohofwoe wrote:
               | The '#ifdef __cplusplus extern "C" { }' thing only
               | removes C++ name mangling from exported symbols, it
               | doesn't switch the C++ language into "C mode"
               | (unfortunately).
        
           | tialaramex wrote:
           | The entire I/O streams (where std::cout comes from) feature
           | is garbage, if this was an independent development there is
           | no way that WG21 would have taken it, the reason it's in C++
           | 98 and thus still here today is that it's Bjarne's baby. The
           | reason not to take it is that it's contradictory to the
           | "Don't use operator overloading for unrelated operations"
           | core idea. Bjarne will insist that "actually" these operators
           | somehow always meant streaming I/O but his evidence is
           | basically the same library feature he's trying to justify. No
           | other language does this, and it's not because they can't
           | it's because it was a bad idea when it was created, it was
           | still a bad idea in 1998, the only difference today is that
           | C++ has a replacement.
           | 
           | The modern fmt-inspired std::print and std::println etc. are
           | much nicer, preserving all the type checking but losing
           | terrible ideas like stored format state, and localisation by
           | default. The biggest problem is that today C++ doesn't have a
           | way to implement this for your own types easily, Barry
           | illustrates a comfortable way this could work in C++ 26 via
           | reflection which on that issue closes the gap with Rust's
           | #[derive(Debug)].
        
             | pjmlp wrote:
             | Perfectly iostreams happy user since 1993.
        
               | codr7 wrote:
               | Same, as long as I stay the hell away from
               | locales/facets.
               | 
               | Type safe input/output stream types and memory backed
               | streams served on a silver plate is a pretty decent
               | improvement over C.
        
               | Dwedit wrote:
               | int a;
               | 
               | cin >> a;
               | 
               | Then the program goes berserk as soon as the first non-
               | number is read out of standard input. All the other "cin
               | >> integer" lines are immediately skipped.
               | 
               | Yes, I know about error checking, clearing error
               | condition, discarding characters. But it's a whole lot of
               | stuff you need to do after every single "cin>>" line. It
               | makes the simplicity of cin not worth it.
        
               | tightbookkeeper wrote:
               | You're holding it wrong. Like nan, the point is you don't
               | have to error check every operation.
               | 
               | You check error for the whole batch.
        
               | eMSF wrote:
               | How could you ever continue after the second statement
               | without checking if you actually read an integer or not?
               | How would you know what you can do with a?
        
               | jvanderbot wrote:
               | You couldn't or wouldn't. but why have a read statement
               | like cin>> which looks so nice and clean when you then
               | have to go and check everything with flags and boolean
               | casts on stateful objects.
               | 
               | I agree. It's lunacy. just be explicit and use functions
               | or equivalent like literally every other language.
        
               | chongli wrote:
               | Well in a language like Haskell you could solve this with
               | monads and do-notation. The general idiom in Haskell is
               | to use a Maybe or Either monad to capture success/failure
               | and you assume you're on the happy path. Then you put the
               | error handling at the consumer end of the pipeline when
               | you unwrap the Maybe or Either.
               | 
               | I believe Rust has adopted similar idioms. I've heard the
               | overall idea referred to as Railway-oriented programming.
               | 
               | In C++ you could implement it with exceptions, though
               | they bring in a bunch of their own baggage that you don't
               | have to deal with when using monads.
        
               | PaulDavisThe1st wrote:
               | fscanf (STDIN, "%d", &a);
               | 
               | the program goes beserk as soon as the first non-number
               | is read out of standard input.
               | 
               | in both cases, you need error checking (which you "know
               | about").
        
               | unwind wrote:
               | No actual C programmer who has been around the block more
               | than halfway should do that. The mantra is: "read into a
               | character buffer, then parse that".
               | 
               | It's more code, sure, but it buys you a lot of good
               | things. I/O is hard.
        
               | PaulDavisThe1st wrote:
               | No C++ programmer who has been around the block more than
               | halfway should do                   cin >> a;
               | 
               | and assume that it works.
               | 
               | But also ...                   sscanf (the_buffer, "%d",
               | &a);
               | 
               | doesn't help the problem in any substantive way.
        
               | tightbookkeeper wrote:
               | Yep, it's very clean once you get the hang of it.
        
               | einpoklum wrote:
               | Then I suppose you don't care about:
               | 
               | * Performance
               | 
               | * Support for localization (as the format string and
               | positions of values to format differ between languages).
               | 
               | * Code reuse & dogfooding - the data structures used in
               | iostreams are not used elsewhere, and vice-versa
               | 
               | * C and OS interoperability - as you can't wrap a stream
               | around a FILE* / file descritor
               | 
               | * bunch of other stuff...
               | 
               | iostreams work, but are rather crappy.
        
               | pjmlp wrote:
               | I care about performance, when it actually matters to
               | acceptance testing.
               | 
               | The less C the merrier.
               | 
               | If you care about correct use of localisation, standard C
               | and C++ libraries aren't really what you're looking for,
               | or even C and C++ to start with.
        
               | wakawaka28 wrote:
               | C and C++ are the bedrock of operating systems with the
               | best performance and extensive support for all languages.
               | 
               | The only reason why iostreams are slow is because of its
               | incompatible buffering scheme, and the fact that C and
               | C++ need to stay in sync when linked together. And that
               | brand of slow is still faster than other languages,
               | except sometimes those that delegate i/o to pure C
               | implementations.
        
               | pjmlp wrote:
               | Historical baggage, they weren't the first system
               | programming languages, got lucky with UNIX's license
               | allowing for widespread adoption, and won't be the last
               | one standing either.
        
               | wakawaka28 wrote:
               | Considering that they are evolving, I think they are more
               | likely than not to stay standing. There might be other
               | similar languages developed in parallel, but after over
               | 30 years of whining C and C++ are still popular. I don't
               | expect that to change.
        
               | throwaway2037 wrote:
               | > If you care about correct use of localisation, standard
               | C and C++ libraries aren't really what you're looking
               | for, or even C and C++ to start with.
               | 
               | What do you recommend instead?
        
               | PaulDavisThe1st wrote:
               | _("some text") ... aka gettext and friends.
        
               | maccard wrote:
               | QT, unfortunately.
        
               | throwaway2037 wrote:
               | Why do you "unfortunately"?
        
               | maccard wrote:
               | It's like using a sledgehammer for a picture hook. I
               | think QT is a great tool, and it solves this problem
               | nicely, but it's a full blown framework, and if you're
               | already using something else - particularly if it's
               | something "light" like SDL, or just a platform specific
               | library like Win32, it's an awful lot to pull in (plus
               | compile times, licensing, etc).
        
               | fra wrote:
               | This was a tip my hatn excellent to you
        
               | johnisgood wrote:
               | Why?
        
             | lugu wrote:
             | Thank you.
        
             | dieortin wrote:
             | > The biggest problem is that today C++ doesn't have a way
             | to implement this for your own types easily
             | 
             | I'm not sure about the stdlib version, but with fmtlib you
             | can easily implement formatters for your own types.
             | https://fmt.dev/11.0/api/#formatting-user-defined-types
        
               | tialaramex wrote:
               | I think the problem is that your idea of "easy" is
               | "Here's a whole bunch of C++ you could write by hand for
               | each type" while the comparison was very literally
               | #[derive(Debug)]. I wasn't abbreviating or referring to
               | something else, that's literally what Rust programmers
               | type to indicate that their type should have the obvious
               | boilerplate implementation for this feature, in most
               | types you're deriving other traits already, so the extra
               | work is literally typing out the word Debug.
        
             | tightbookkeeper wrote:
             | What's wrong with it?
        
             | wakawaka28 wrote:
             | >No other language does this, and it's not because they
             | can't it's because it was a bad idea when it was created,
             | it was still a bad idea in 1998, the only difference today
             | is that C++ has a replacement.
             | 
             | Hindsight is 20/20, remember that. Streams are not that bad
             | of an idea and have been working fine for decades. You
             | haven't named a problem with it other than the fact the
             | operators are used for other stuff in other contexts. But
             | operator overloading is a feature of C++ so most operators,
             | even the comma operator, can be something other than what
             | you expect.
             | 
             | >The biggest problem is that today C++ doesn't have a way
             | to implement this for your own types easily, Barry
             | illustrates a comfortable way this could work in C++ 26 via
             | reflection which on that issue closes the gap with Rust's
             | #[derive(Debug)].
             | 
             | You can trivially implement input and output for your own
             | types with streams.
             | 
             | You appear to be a Rust guy whose motive is to throw shade
             | on C++ for things that are utterly banal and subjective
             | issues.
        
               | PaulDavisThe1st wrote:
               | What they mean is this:                    struct Foo {
               | int a;            float b;            std::string c;
               | };                    Foo foo;          std::cout << foo;
               | 
               | with no extra code. It's called reflection, where the
               | compiler can generate good-enough code to generate a
               | character-stream serialization of an object without any
               | human intervention.
        
               | wakawaka28 wrote:
               | I know what reflection is of course. C++ makes it easy to
               | implement IO. If you're asking for a reflection-based
               | solution with less effort, you are practically asking for
               | zero extra code. Anyway, C++ does not yet have reflection
               | but who's to say how anyone wants any particular data to
               | be dumped? A default implementation is nice but less
               | useful than you make it sound. In any case, there are
               | libraries approximating what you described (usually with
               | macros and stuff) and reflection is totally coming at
               | some point.
        
             | spacechild1 wrote:
             | Remember that C++ originally didn't have variadic
             | templates, so something like std::format would have been
             | impossible back in the day. Back in the day, std::iostream
             | was a very neat solution for _type safe_ string formatting.
             | As you conceded, it also makes it very easy to integrate
             | your own types. It was a _big_ improvement over printf().
             | Historic perspective is everything.
        
               | vitaut wrote:
               | {fmt} used to support pre-C++11 compilers that didn't
               | have variadic templates. It was a pain to emulate
               | variadics but it wasn't impossible.
        
               | spacechild1 wrote:
               | Wow, I don't know that! I would be curious to know how
               | they did it.
        
             | throwaway2037 wrote:
             | Over the years, I have heard numerous complaints about C++
             | I/O streams. Is there a better open source replacement? Or
             | do you recommend to use C functions for I/O?
        
               | steveklabnik wrote:
               | https://en.cppreference.com/w/cpp/utility/format for
               | C++20 and https://en.cppreference.com/w/cpp/io/print for
               | C++23
               | 
               | https://github.com/fmtlib/fmt is what it's based on, for
               | C++11 and up support.
        
             | alexvitkov wrote:
             | > Don't use operator overloading for unrelated operations
             | 
             | This disn't stop with <iostream>, they keep doing it - the
             | latest example I can think of is std::ranges operations
             | being "piped" with |.
        
             | chipdart wrote:
             | > The entire I/O streams (where std::cout comes from)
             | feature is garbage, if this was an independent development
             | there is no way that WG21 would have taken it, the reason
             | it's in C++ 98 and thus still here today is that it's
             | Bjarne's baby.
             | 
             | I think this is a very lazy and somewhat conspiratorial
             | take.
             | 
             | C++'s IO stream library, along with C++'s adoption of
             | std::string, is a response to and improvement over C's
             | standard library support for IO. That alone makes it an
             | invaluable improvement. It's easy and very lazy to look
             | back 30 years ago and badmouth things done back then.
             | 
             | It's also easy to complain about no one proposing changes
             | when literally anyone, including you, can propose changes.
             | The only need to do the legwork and put their money where
             | their mouth is. The funny part is that we see frameworks
             | putting together their own IO infrastructure and it ends up
             | being not good, such as Qt's take on IO.
             | 
             | But talk is cheap and badmouthing doesn't require a pull
             | request.
        
           | Someone wrote:
           | > C++ code can seamlessly add #include "sqlite3.h" unchanged.
           | 
           | Almost seamlessly. You have to do                 extern "C"
           | {         #include "sqlite3.h"       }
           | 
           | (https://isocpp.org/wiki/faq/mixing-c-and-cpp#include-c-
           | hdrs-...)
        
             | cornstalks wrote:
             | If we're nitpicking then sqlite3.h already has `#ifdef
             | __cplusplus` and `extern "C" {`. So yes, from the user's
             | perspective it is seamless. They do not need to play the
             | `extern "C" {` game.
        
           | rramadass wrote:
           | Yep; I think of it as "C/C++" and not "C" and/or "C++" i.e.
           | one "multi-paradigm" language with different sets of mix-and-
           | match.
        
             | varjag wrote:
             | kinda like python and ruby
        
         | MathMonkeyMan wrote:
         | My brief foray into microcontroller land has taught me that C
         | and C++ are very much mixed.
         | 
         | It's telling that every compiler toolchain that compiles C++
         | also compiles C (for some definition of "C"). With compiler
         | flags, GCC extensions, and libraries that are kinda-sorta
         | compatible with both languages, there's no being strict about
         | it.
         | 
         | _My_ code might be strict about it, but what about tinyusb?
         | Eventually you'll have to work with a library that chokes on
         | `--pedantic`, because much (most?) code is not written to a
         | strict C or C++ standard, but is "C/C++" and various
         | extensions.
        
           | rramadass wrote:
           | > because much (most?) code is not written to a strict C or
           | C++ standard, but is "C/C++" and various extensions.
           | 
           | Absolutely true. I generally insist on folks learning C and
           | C++ interoperability before diving in to all the "Modern C or
           | C++" goodness. It helps them in understanding what actually
           | is going on "under the hood" and makes them a better
           | programmer/debugger.
           | 
           | See also the book _Advanced C and C++ Compiling by Milan
           | Stevanovic._
        
         | emmelaich wrote:
         | If you want a language with a great C FFI, C++ is hard to beat!
        
         | sim7c00 wrote:
         | can't believe so many people are arguing against this honestly.
         | you don't mix them in the sense the author means. I take it
         | these people didn't read the paragraphs this was the 'takeaway'
         | from.
         | 
         | For example, the primary reason for the sentence seems to be
         | from the text: "Many code examples in this book won't even
         | compile on a c++ compiler, So we should not mix sources of both
         | languages".
         | 
         | It's not at all about the ability to use c libraries in c++
         | projects or vice versa :S.... c'mon guys!
        
       | bitbasher wrote:
       | Really looking forward to #embed, once the compilers catch up.
       | Until then, Golang.
        
         | enriquto wrote:
         | This is not how C standards work. If it appears in the
         | standard, it means that it is already implemented in some
         | compilers (in that case, at least in gcc and clang).
        
           | pjmlp wrote:
           | That isn't really how it goes, that is how it used to be up
           | to C99.
        
             | enriquto wrote:
             | Thanks for the correction! Do you know if there is a
             | document from the standards body explaining the change in
             | philosophy?
        
               | MathMonkeyMan wrote:
               | I've heard something along the lines of "the standard is
               | to define facilities that will be used in most programs,
               | and to codify widespread existing practice." That was in
               | the context of "I don't like this proposed feature,"
               | though. This was for C++, not C.
               | 
               | A lot of stuff in the C++11 standard library was based on
               | widespread use of Boost. Since then, I don't know. Also,
               | were things like templates and lambdas implemented as
               | compiler extensions before standardization? I don't know,
               | but I doubt it. Maybe "we're a committee of people who
               | will decide on a thing and we hope you like it" was
               | always the norm in many ways.
        
               | JonChesterfield wrote:
               | It's a nuisance to implement the thing you want to add to
               | the standard yourself. It's easier to ship it in the
               | language and then complain at compiler devs that they're
               | running behind the edge of progress.
               | 
               | This interacts in the obvious way with refusing to
               | correct mistakes after the fact for fear of breaking user
               | code.
               | 
               | I don't believe anyone has written a paper along the
               | lines of "let's not bother with the existing practice
               | part anymore", it's more an emergent feature of people
               | following local incentive structures.
        
         | rfl890 wrote:
         | Clang 19 has it.
        
         | jpcfl wrote:
         | Or                   xxd --include <file>
         | 
         | :)
        
           | Keyframe wrote:
           | The anti-Rust approach!
        
         | f1shy wrote:
         | I do that with ld and objcopy:
         | 
         | https://stackoverflow.com/questions/58815959/include-binary-...
        
         | accelbred wrote:
         | I end up using a .S asm file with .incbin directives to embed
         | files.
         | 
         | #embed would be much nicer
        
           | JonChesterfield wrote:
           | Incbin works just fine from inline asm fwiw
        
             | flohofwoe wrote:
             | Inline assembly isn't supported for x86-64 and ARM on MSVC
             | which unfortunately also means the incbin trick can't be
             | used there anymore.
        
       | enriquto wrote:
       | So happy that we still get the dinosaur mascots! This is a good
       | book.
        
       | auggierose wrote:
       | Table of contents in the sidebar doesn't work properly for me
       | when I click on an entry (in macOS Preview).
        
         | bwidlar wrote:
         | I just test some links in the table of content, works fine for
         | me. Using zathura pdf reader.
        
           | Jtsummers wrote:
           | Also works in Adobe and Firefox, but doesn't work in Chrome
           | and Edge.
        
         | f1shy wrote:
         | Doesn't work for me either... but I will not dismiss the book
         | because of that.
        
         | soegaard wrote:
         | Same here.
        
         | channel_t wrote:
         | Table of contents is definitely broken right now.
        
       | jumpman_miya wrote:
       | in example 1.1 i read that as 'tits_square' until i saw the
       | output
        
         | glass-z13 wrote:
         | that's by design
        
           | ykonstant wrote:
           | It's a booby trap.
        
       | zkirill wrote:
       | I was going to ask if there is a good list of C books and then
       | answered my own question. It categorizes _Modern C_ as
       | Intermediate level.
       | 
       | https://stackoverflow.com/questions/562303/the-definitive-c-...
        
         | emmanueloga_ wrote:
         | Note that this is not a complete list, fwiw. For example, I
         | doesn't include "Effective C." [1].
         | 
         | I like "Effective C" over "Modern C" because it's more engaging
         | ... "Modern C" is super rigorous and feels a bit like reading
         | an annotated spec of the language, which is what an expert may
         | need, but makes for a dull read for a casual C user like me.
         | 
         | --
         | 
         | 1: https://nostarch.com/effective-c-2nd-edition
        
           | xbar wrote:
           | I agree, but I think Modern C has good, structured
           | recommendations that make it worth getting through at least
           | once.
        
         | rramadass wrote:
         | Also see _Fluent C: Principles, Practices and Patterns_ by
         | Christopher Preschern.
        
         | xbar wrote:
         | I like Modern C. I have reviewed it favorably in several
         | places. I agree it is intermediate.
         | 
         | I think 21st Century C by Ben Klemens and C Programming a
         | Modern Approach by King are both more approachable alternatives
         | as a modern C companions to K&R.
        
       | ralphc wrote:
       | How does "Modern" C compare safety-wise to Rust or Zig?
        
         | renox wrote:
         | You'd be surprised: Zig has one UB (Undefined Behaviour) that C
         | doesn't have!
         | 
         | In release fast mode, unsigned overflow/underflow is undefined
         | in Zig whereas in C it wraps.
         | 
         | :-)
         | 
         | Of course C has many UBs that Zig doesn't have, so C is far
         | less safe than Zig, especially since you can use ReleaseSafe in
         | Zig..
        
           | uecker wrote:
           | UB is does not automatically make things unsafe. You can have
           | a compiler that implements safe defaults for most UB, and
           | then it is not unsafe.
        
             | ahoka wrote:
             | By definition UB cannot be safe.
        
               | marssaxman wrote:
               | this depends on your choice of definition for "safe"
        
               | Maxatar wrote:
               | The definition given by the C standard allows for safe
               | undefined behavior.
        
               | umanwizard wrote:
               | Something can be UB according to the standard, but
               | defined (and safe) according to a particular
               | implementation. Lots of stuff is UB according to the C or
               | C++ standard but does something sensible in gcc and/or
               | clang.
        
             | duped wrote:
             | That's implementation defined behavior, not undefined
             | behavior. Undefined behavior explicitly refers to something
             | the compiler does not provide a definition for, including
             | "safe defaults."
        
               | fuhsnn wrote:
               | Compilers are not prohibited to provide their own
               | definition for UB, that's how UBsan exists.
        
               | Maxatar wrote:
               | The C standard says, and I quote:
               | 
               | >Possible undefined behavior ranges from ignoring the
               | situation completely with unpredictable results ... or
               | program execution in a documented manner characteristic
               | of the environment (with or without the issuance of a
               | diagnostic message)
               | 
               | So a compiler is absolutely welcome to make undefined
               | behavior safe. In fact every compiler I know of, such as
               | GCC, clang, MSVC has flags to make various undefined
               | behavior safe, such as signed integer overflow, type
               | punning, casting function pointers to void pointers.
               | 
               | The Linux kernel is notorious for leveraging undefined
               | behavior in C for which GCC guarantees specific and well
               | defined behavior.
               | 
               | It looks like there is also the notion of unspecified
               | behavior, which gives compilers a choice about the
               | behavior and does not require compilers to document that
               | choice or even choose consistently.
               | 
               | And finally there is what you bring up, which is
               | implementation defined behavior which is defined as a
               | subset of unspecified behavior in which compilers must
               | document the choice.
        
             | renox wrote:
             | Well Zig has ReleaseSafe for this.. ReleaseFast is for
             | using these UBs to generate the fastest code.
        
           | secondcoming wrote:
           | Does C automatically wrap? I thought you need to pass
           | `-fwrapv` to the compiler to ensure that.
        
             | greyw wrote:
             | Unsigned overflow wraps. Signed overflow is undefined
             | behavior.
        
               | kbolino wrote:
               | This distinction does not exist in K&R 2/e which
               | documents ANSI C aka C89, but maybe it was added in a
               | later version of the language (or didn't make it into the
               | book)? According to K&R, all overflow is undefined.
        
               | wahern wrote:
               | I don't have my copy of K&R handy, but this distinction
               | has existed since the initial codification. From C89:
               | 3.1.2.5 Types            [...] A computation involving
               | unsigned operands can never overflow, because a result
               | that cannot be represented by the resulting unsigned
               | integer type is reduced modulo the number that is one
               | greater than the largest value that can be represented by
               | the resulting unsigned integer type.
               | 
               | Source: C89 (draft) at
               | https://port70.net/~nsz/c/c89/c89-draft.txt
        
             | renox wrote:
             | -fwrapv is for _signed_ integer overflow not _unsigned_.
        
               | sp1rit wrote:
               | Yes, as unsigned overflow is fine by default. AFAIK the
               | issue was originally that there were still machines that
               | used ones complement for describing negative integers
               | instead of the now customary twos complement.
        
         | WalterBright wrote:
         | Modern C still promptly decays an array to a pointer, so no
         | array bounds checking is possible.
         | 
         | D does not decay arrays, so D has array bounds checking.
         | 
         | Note that array overflow bugs are consistently the #1 problem
         | with shipped C code, by a wide margin.
        
           | layer8 wrote:
           | > no array bounds checking is possible.
           | 
           | This isn't strictly true, a C implementation is allowed to
           | associate memory-range (or more generally, pointer
           | provenance) metadata with a pointer.
           | 
           | The DeathStation 9000 features a conforming C implementation
           | which is known to catch all array bounds violations. ;)
        
             | uecker wrote:
             | Right. Also it might it sound like array-to-pointer decay
             | is forced onto the programmer. Instead, you can take the
             | address of an array just fine without letting it decay. The
             | type then preserves the length.
        
               | codr7 wrote:
               | Nice, when you know the length at compile time, which is
               | rarely from my experience.
               | 
               | The holy grail is runtime access to the length, which
               | means an array would have to be backed by something more
               | elaborate.
        
               | uecker wrote:
               | Oh, it also work for runtime length:
               | 
               | https://godbolt.org/z/PnaWWcK9o
        
               | pjmlp wrote:
               | Now try that on a compiler without -fsanitize=bounds, yet
               | full ISO C compliant.
        
               | uecker wrote:
               | You can still access the size which is what the parent
               | was asking for. And please tell me how you would try this
               | on an ISO compliant compiler for D.
        
               | pjmlp wrote:
               | D has bounds checking, and isn't a ISO language.
        
               | WalterBright wrote:
               | C: int foo(int a[]) { return a[5]; }
               | int main() {             int a[3];             return
               | foo(a);         }              > gcc test.c         >
               | ./a.out
               | 
               | Oops.
               | 
               | D: int foo(int[] a) { return a[5]; }
               | int main() {             int[3] a;             return
               | foo(a);         }              > ./cc array.d         >
               | ./array
               | core.exception.ArrayIndexError@array.d(1): index [5] is
               | out of bounds for array of length 3
               | 
               | Ah, Nirvana!
               | 
               | How to fix it for C:
               | 
               | https://www.digitalmars.com/articles/C-biggest-
               | mistake.html
        
               | uecker wrote:
               | You need to take the address of the array instead of
               | letting it decay and then size is encoded in the type:
               | int foo(int (*a)[6]) { return a[5]; }       int main() {
               | int a[3];         return foo(&a);       }
               | 
               | Or for run-time length:                 int foo(int n,
               | int (*a)[n]) { return (\*a)[5]; }       int main() {
               | int a[3];         return foo(ARRAY_SIZE(a), &a);       }
               | /app/example.c:4:38: runtime error: index 5 out of bounds
               | for       type 'int[n]'
               | 
               | https://godbolt.org/z/dxx7TsKbK\*
        
               | WalterBright wrote:
               | int foo(int n, int (*a)[n]) { return (\*a)[5]; }
               | int main() {         int a[3];         return
               | foo(ARRAY_SIZE(a), &a);       }
               | 
               | That syntax is why array overflows remain the #1 problem
               | with C bugs in shipped code. It isn't any better than:
               | int foo(size_t n, int* a) { assert(5 < n); return a[5]; }
               | int main() {         int a[3];         return
               | foo(ARRAY_SIZE(a), a);       }
               | 
               | as the array dimension has to be handled separately from
               | the pointer.
               | 
               | Contrast with how simple it is in D:
               | int foo(int[] a) { return a[5]; }         int main() {
               | int[3] a;             return foo(a);         }
               | 
               | and the proof is shown by array overflow bugs in the wild
               | are stopped cold. It can be that simple and effective in
               | C.
        
               | ryao wrote:
               | This should be caught by CHERI.
        
             | TZubiri wrote:
             | "The DeathStation 9000"
             | 
             | The what now?
        
               | layer8 wrote:
               | Google it.
        
               | TZubiri wrote:
               | Yeah, why have any type of human interaction in a forum
               | when you can just refer your fellow brethren to the
               | automaton.
        
               | layer8 wrote:
               | I'm saying this because any explanation I could offer
               | would provide less insight than the Google results.
        
               | TZubiri wrote:
               | Less insight, perhaps, but of higher quality, which is
               | subjective.
               | 
               | I personally find that googling stuff provides not much
               | connection to the subject of study, very impersonal and
               | try to avoid it.
               | 
               | For example I did google the concept, and found this
               | https://github.com/cousteaulecommandant/ds9k.
               | 
               | Which is not trivial to parse, bing posited the answer as
               | authoritative, and if you look at the code it is really
               | nothing, it seems to be a folklore concept, and as such,
               | it is much more aptly transmitted by speaking to a human
               | and getting a live version than by googling an
               | authoratitative static answer.
        
               | bsder wrote:
               | Nasal daemons for those of us of a slightly older vintage
               | ...
        
             | trealira wrote:
             | > The DeathStation 9000 features a conforming C
             | implementation which is known to catch all array bounds
             | violations. ;)
             | 
             | That actually really does exist already with CHERI CPUs,
             | whose pointers are tagged with "capabilities," which catch
             | buffer overruns at runtime.
             | 
             | https://tratt.net/laurie/blog/2023/two_stories_for_what_is_
             | c...
             | 
             | https://msrc.microsoft.com/blog/2022/01/an_armful_of_cheris
             | /
        
             | Rusky wrote:
             | A worked example: https://github.com/pizlonator/llvm-
             | project-deluge/blob/delug...
        
           | sdk77 wrote:
           | The thing is though that even with array bounds checking
           | built into the language, out of bounds access due to
           | programming error can still be attempted. Only this time it's
           | safer because an attacker can't use the bug (which still
           | exists) to access memory outside of bounds. In any case, the
           | program still doesn't work as intended (has bugs) because the
           | programmer has attempted, or allowed the attempt, to access
           | out of bounds memory.
           | 
           | Writing safe code is better than depending on safety
           | features. Writing safe code is possible in any programming
           | language, the only things required are good design principles
           | and discipline (i.e. solid engineering).
        
             | WalterBright wrote:
             | In practice in C, that does not work because array overflow
             | bugs are still the #1 bug in shipped C code, by a wide
             | margin.
        
         | jandrese wrote:
         | Modern C is barely any different than older C. The language
         | committee for C is extremely conservative, changes tend to
         | happen only around the edges.
        
           | flohofwoe wrote:
           | Except for C99 which added designated init and compound
           | literals. With those it almost feels like a new language
           | compared to C89 (and the C99 designated init feature is so
           | well thought out that it still beats most similar
           | initialization patterns in more recent languages, including
           | C++, Rust and Zig - only Odin seems to "get it").
        
         | pornel wrote:
         | There's finally a way to safely add two signed numbers, without
         | tricky overflow checks that may trigger UB themselves!
        
       | zerr wrote:
       | Do they still use 0-terminated strings/char* as the main string
       | type?
       | 
       | Is the usage of single linked lists still prevalent as the main
       | container type?
        
         | racingmars wrote:
         | > Do they still use 0-terminated strings/char* as the main
         | string type?
         | 
         | Of course, it's still C.
         | 
         | > Is the usage of single linked lists still prevalent as the
         | main container type?
         | 
         | As far as I can remember, the C standard library has never had
         | any functions that used linked lists. Nor are there any
         | container types, linked lists or otherwise, provided by C. So
         | I'd say this is a question about how people teach and use C,
         | not related to the language -- or language spec version --
         | itself.
        
           | eps wrote:
           | Don't feed the trolls.
        
           | zerr wrote:
           | I don't mean the language spec but what is commonly used in
           | the wild.
        
         | KerrAvon wrote:
         | The C standard library provides no recognizable container
         | types, so there's no "main" anything.
        
         | codr7 wrote:
         | Embedded linked lists are pretty cool though.
        
           | ithkuil wrote:
           | aka intrusive linked lists
        
         | EasyMark wrote:
         | I don't think that will ever change. Will the possibly
         | introduce a more modern string2 type? Maybe but it will
         | probably be unlikely before 2050
        
       | russellbeattie wrote:
       | Wow, the use of attributes like [[__unsequenced__]],
       | [[maybe_unused]] and [[noreturn]] throughout the book is really
       | awful. It seems pretty pedantic of the author to litter all the
       | code examples with something that is mostly optional. For a
       | second I wondered if C23 _required_ them.
        
         | amomchilov wrote:
         | Such is the issue with bad defaults. Opting into the sensible
         | thing makes most of your code ugly, instead of just the
         | exceptions.
        
       | nimish wrote:
       | My kingdom for fully specified, well defined portable bitfields.
        
       | leonheld wrote:
       | One of my favorite books ever.
        
       | einpoklum wrote:
       | It's only been a few years since I've come to feel I can rely on
       | C compilers all supporting C99, for a library I'm maintaing [1].
       | And after a couple of years, sure enough - I get an issue opened
       | asking for C89 compatibility because of some arcane embedded
       | toolchain or what-not.
       | 
       | So, C23? ... that's nice and all, but, let's talk about it in 20
       | years or so T_T
       | 
       | [1]: https://github.com/eyalroz/printf
        
       | jhatemyjob wrote:
       | Can someone link me to an article that explains why C is
       | basically frozen at C99 for all practical purposes? Few projects
       | worth talking about leverage features from C11 and newer
        
         | pornel wrote:
         | C99 is still _new_! Microsoft tried to kill C by refusing to
         | implement anything that wasn 't also in C++. MSVC was 16 years
         | late implementing C99, and implemented only the bare minimum.
         | Their C11 implementation is only 11 years late.
         | 
         | I suspect that decades of C being effectively frozen have
         | caused the userbase to self-select to people who like C exactly
         | the way it is (was), and don't mind supporting ancient junk
         | compilers.
         | 
         | Everyone who lost patience, or wanted a 21st century language,
         | has left for C++/Rust/Zig or something else.
        
           | uecker wrote:
           | Most of us liking a good language just did not use MSVC. I do
           | not think many people who appreciate C's simplicity and
           | stability would be happy with C++ / Rust. Zig is beautiful,
           | but still limited in many ways and I would not use it outside
           | of fun projects.
        
             | pornel wrote:
             | I don't even use Windows, but I need to write portable
             | libraries. Unfortunately, MSVC does strongly influence the
             | baseline, and it's not my decision if I want to be
             | interoperable with other projects.
             | 
             | In my experience, Windows devs don't like being told to use
             | a different toolchain. They may have projects tied to
             | Visual Studio, dependencies that are MSVC-only or code
             | written for quirks of MSVC's libc/CRT, or want unique MSVC
             | build features.
             | 
             | I found it hard to convince people that C isn't just C
             | (probably because C89 has been around forever, and many
             | serious projects still target it). I look like an asshole
             | when I demand them to switch to whole another toolchain,
             | instead of me adding a few #ifdefs and macro hacks for some
             | rare nice thing in C.
             | 
             | Honestly, paradoxically it's been easier to tell people to
             | build Rust code instead (it has MSVC-compatible output with
             | almost zero setup needed).
        
               | uecker wrote:
               | The good news is that MSVC has C17 support (still missing
               | important optional features, but at least some progress).
        
         | flohofwoe wrote:
         | Microsoft basically sabotaged C99 by not implementing any of
         | its features until around 2015 in the Visual Studio C compiler,
         | and then still took until 2019 before they acknowledged their
         | failure and started supporting more recent C versions again
         | (MSVC is still reliably behind Clang and GCC when it comes to
         | their C frontend though).
         | 
         | And back around 2010 MSVC still mattered a lot (which sounds
         | weird from today's pov where most developers appear to have
         | moved to Linux).
         | 
         | But OTH, few projects actually need C11 features (and C11
         | actually took one thing away from C99: VLAs - nothing of value
         | was lost though).
         | 
         | C23 might be the first version since C99 that's actually worth
         | upgrading to for many C code bases.
        
           | spacechild1 wrote:
           | C11 gave us one very important thing: a standardized memory
           | model! Just like in C++11, you can finally write cross-
           | platform multithreaded code with standardized atomics,
           | synchronization primitives and threads. Unfortunately,
           | compiler/library support is still lacking...
        
       | kristianp wrote:
       | GCC support has been around since gcc 11 apparently. See table at
       | (1). This is available in ubuntu 22.04. The page below also shows
       | support for C26!
       | 
       | 1) https://gcc.gnu.org/projects/cxx-
       | status.html#:~:text=C%2B%2B...
        
         | bondant wrote:
         | That's for C++ not C
        
       | johnisgood wrote:
       | Personally this[1] just makes C much more complicated for me, and
       | I choose C when I want simplicity. If I want complicated, I would
       | just pick C++ which I typically would never want. I would just
       | pick Go (or Elixir if I want a server).
       | 
       | "_BitInt(N)" is also ugly, reminds me of "_Bool" which is
       | thankfully "bool" now.
       | 
       | [1] guard, defer, auto, constexpr, nullptr (what is wrong with
       | NULL?), etc. On top of that "constexpr" and "nullptr" just reeks
       | of C++.
       | 
       | That said, Modern C is an incredible book, I have been using it
       | for C99 (which I intend to continue sticking to).
        
         | cornstalks wrote:
         | > _what is wrong with NULL?_
         | 
         | For starters, you have to #include a header to use it.
        
           | zik wrote:
           | And it avoids the NULL == 0 ambiguity, allowing for better
           | type checking.
        
           | johnisgood wrote:
           | Well, I always include stdio.h which includes stddef.h that
           | defines NULL as (void *)0.
        
             | dhhfss wrote:
             | In my experience, hardly any source files require studio.h
             | 
             | stddef.h on the other hand is required by most to get
             | size_t
        
               | johnisgood wrote:
               | You are right. Hereby I correct my parent comment: I
               | talked about my own personal experience[1], but yeah, as
               | you said, stddef.h is often required (and yes, often I do
               | not need stdio.h, stddef.h is what I need) which defines
               | NULL, which was my point. If it is often required, then
               | it does not matter whether you have to include a header
               | file or not, IMO.
               | 
               | Just include the stddef.h header if you want to use NULL,
               | similarly to how you include a header file if you want to
               | use anything else, e.g. bool from stdbool.h.
               | 
               | [1] I am not entirely sure in retrospect, actually, as I
               | might be misremembering, but my point stands with or
               | without stdio.h!
        
         | mmphosis wrote:
         | NULL is not wrong. The things that I will do with NULL are
        
         | nickelpro wrote:
         | > what is wrong with NULL?
         | 
         | One of the few advantages of ISO standardization is you can
         | just read the associated papers to answer questions like this:
         | https://wg21.link/p2312
         | 
         | The quick bullet points:
         | 
         | * Surprises when invoking a type-generic macro with a NULL
         | argument.
         | 
         | * Conditional expressions such as (1 ? 0 : NULL) and (1 ? 1 :
         | NULL) have different status depending how NULL is defined
         | 
         | * A NULL argument that is passed to a va_arg function that
         | expects a pointer can have severe consequences. On many
         | architectures nowadays int and void* have different size, and
         | so if NULL is just 0, a wrongly sized argument is passed to the
         | function.
        
         | flohofwoe wrote:
         | auto is mostly useful when tinkering with type-generic macros,
         | but shouldn't be used in regular code (e.g. please no 'almost
         | always auto' madness like it was popular in the C++ world for a
         | little while). Unfortunately there are also slight differences
         | between compilers (IIRC Clang implements a C++ style auto,
         | while GCC implements a C style auto, which has subtle
         | differences for 'auto pointers' - not sure if those differences
         | have been fixed in the meantime).
         | 
         | _BitInt(N) isn't typically used directly but typedef'ed to the
         | width you need, e.g.                   typedef _BitInt(2) u2;
         | 
         | The 'ugly' _B syntax is needed because the combination of
         | underscore followed by a capital letter is reserved in the C
         | standard to avoid collisions with existing code for every
         | little thing added to the language (same reason why it was
         | called _Bool).
         | 
         | AFAIK defer didn't actually make it into C23?
         | 
         | I'm also more on the conservative side when it comes to adding
         | features to the C standard, but IMHO each of the C23 additions
         | makes sense.
        
           | humanrebar wrote:
           | > IIRC Clang implements a C++ style auto, while GCC
           | implements a C style auto, which has subtle differences for
           | 'auto pointers' - not sure if those differences have been
           | fixed in the meantime
           | 
           | Both have compatibly implemented the standard C++ auto. Since
           | 2011 or so.
        
             | flohofwoe wrote:
             | Well, not in C :)
             | 
             | Here's an example where Clang and GCC don't agree about the
             | behaviour of auto in C23:
             | 
             | https://www.godbolt.org/z/WchMK18vx
             | 
             | IIRC Clang implements 'C++ semantics' for C23 auto, while
             | GCC doesn't.
             | 
             | Last time I brought that up it turned out that both
             | behaviours are 'standard compliant', because the C23
             | standard explicitly allows such differing behaviour (it
             | basically standardized the status quo even if different
             | compilers disagreed about auto semantics in C).
             | 
             | PS: at least Clang has a warning now in pedantic mode:
             | https://www.godbolt.org/z/ovj5r4axn
        
               | johnisgood wrote:
               | This difference of implementation in two of the major C
               | compilers leaves a bad taste in my mouth. :/
        
               | cpeterso wrote:
               | > PS: at least Clang has a warning now in pedantic mode:
               | https://www.godbolt.org/z/ovj5r4axn
               | 
               | Did you mean gcc? Your link shows a gcc error:
               | <source>:3:5: error: 'auto' requires a plain identifier,
               | possibly with attributes, as declarator           3 |
               | auto* p = &i;             |     ^~~~
        
               | flohofwoe wrote:
               | No, GCC is right to error there, because the code uses a
               | C++-ism (the '*' after 'auto' only makes sense in C++ but
               | not in C).
        
           | eqvinox wrote:
           | > AFAIK defer didn't actually make it into C23?
           | 
           | Correct, defer didn't make it into C23.
           | 
           | It (in its __attribute__((cleanup())) form) is also one of
           | the most useful extensions in GCC/clang -- but, again, for
           | use in macros.
        
         | josefx wrote:
         | > (what is wrong with NULL?)
         | 
         | The old definition did not even specify wether it was a pointer
         | or an integer. So for platforms that did not follow the Posix
         | ((void*)0) requirement it was a foot gun that had neither the
         | type nor the size of a pointer.
         | 
         | > On top of that "constexpr" and "nullptr" just reeks of C++.
         | 
         | Probably because they where back ported from C++. You can still
         | use NULL, since that was apparently redefined to be nullptr.
        
           | johnisgood wrote:
           | What platforms are those that are in use, and how widespread
           | their use is?
        
         | consteval wrote:
         | > If I want complicated, I would just pick C++ which I
         | typically would never want
         | 
         | In my opinion, complexity doesn't scale linearly like this.
         | Sometimes, in fact often times, having more complex tools means
         | a simpler process and end result.
         | 
         | It's like building a house. A hammer and screwdriver are very
         | simple. A crane is extremely complex. But which simplifies
         | building a house? A crane. If I wanted to build a house with
         | only a hammer and screwdriver, I would have to devise
         | incredibly complex processes to get it done.
         | 
         | You see the same type of thing in programming languages. Making
         | a generic container in C++ is trivial. It's very, very hard in
         | C. You can make it kind of generic. You can use void * and do a
         | bunch of manual casting. But it's cumbersome, error prone, and
         | the code is more complex. It's counter-intuitive - how can C, a
         | simpler language, produce code that is more complex than C++?
         | 
         | Or look at std::sort vs qsort. The power of templates and
         | functors makes the implementation much simpler - and faster! We
         | don't have to pass around void * and dereference them at
         | runtime, instead we can build in comparison into the definition
         | of the function itself. No redirection, no passing on the
         | stack, and we can even go so far as to inline the comparison
         | function.
         | 
         | There's really lots of examples of this kind of stuff. Point
         | being, language complexity does not imply implementation
         | complexity.
        
       | israrkhan wrote:
       | Most important aspect of C is its portability. From small
       | microcontrollers to almost any computing platform. I doubt that
       | any new version of C will see that much adoption.
       | 
       | If I want to live on cutting edge I would rather use C++2x or
       | Rust rather than C.
       | 
       | Am I missing something? What benefit this supposedly modern C
       | offers?
        
         | vitaminka wrote:
         | these features will eventually trickle down into the
         | mainstream, kind of like C11 is doing at the moment
         | 
         | also, unless you're targeting embedded or a very wide set of
         | architectures, there's no reason why you couldn't start using
         | C23 today
        
           | bboygravity wrote:
           | Or in other words, for embedded and existing code: most use
           | c99, some use c11 and nobody uses c23 until at least 10 years
           | from now.
        
             | dhhfss wrote:
             | This depends on the platform. Many embedded systems are
             | based on arm these days and have modern toolchains
             | available.
             | 
             | I cannot remember the last time I saw C99 used. C codebases
             | generally use C11 or C17, and C++ code bases use C++20
        
               | pjmlp wrote:
               | Unless you can vouch for the C++ compiler, the best C++
               | portable code can offer today is C++17.
               | 
               | Also 8 and 16 bit embedded toolchains are certainly not
               | on C11 / C17, they can hardly afford full C89.
        
               | flohofwoe wrote:
               | SDCC is a niche C compiler for 8-bit CPUs and is more
               | uptodate than MSVC ;P
               | 
               | https://sdcc.sourceforge.net/
               | 
               | That's the nice thing with C: it's much easier for small
               | teams to fully support than the latest C++ standards.
        
               | pjmlp wrote:
               | Now try to use it on the embedded deployments that
               | require certification.
        
             | vitaminka wrote:
             | most non-embedded and non-legacy codebases could use c23,
             | that's not an insignificant set
        
         | doe_eyes wrote:
         | Of course they will, just like they did in the past with C11,
         | GNU extensions, or some of the individual features that are now
         | rolled into C23. For example, the 0b notation for binary
         | numbers is widely used in the MCU world.
         | 
         | The microcontroller toolchains are generally built on top of
         | GCC, so they get the features for free. There are some
         | proprietary C compilers that are chronically lagging behind,
         | but they are not nearly as important as they used to be two
         | decades ago.
        
         | shakna wrote:
         | The `thread_local` specifier is used on a few microcontroller
         | platforms already, but would be absolutely illegal in C11 and
         | before to use. However, it vastly simplifies memory management
         | in a threaded context.
         | 
         | Why would I rather step into the world of C++ just to deal with
         | that?
        
           | casenmgreen wrote:
           | IIRC, performance and cost of thread local store varies
           | greatly between platforms.
           | 
           | You have to know what you're biting into, before you use
           | that.
        
         | flohofwoe wrote:
         | One advantage of writing C code is that you don't have annoying
         | discussions about what idiomatic code is supposed to look like,
         | and what language subset is the right one ;)
         | 
         | For the cutting edge I would recommend Zig btw, much less
         | language complexity than both modern C++ and Rust.
         | 
         | One good but less visible side effect of C23 is that it
         | harmonizes more syntax with C++ (like ... = {} vs {0}) which
         | makes it a bit less annoying for us C library maintainers to
         | support the people how want to compile their C code with a C++
         | compiler.
        
           | pjmlp wrote:
           | There is enough material in C, and related compiler
           | extensions, to have similar discussions, starting from where
           | to place brackets.
        
             | rbanffy wrote:
             | Maybe the C24 will define the One Right Way.
        
           | Arch-TK wrote:
           | > C library maintainers to support the people how want to
           | compile their C code with a C++ compiler.
           | 
           | Just tell them to go away.
           | 
           | Trying to write the subset of C and C++ is a fool's errand.
        
             | jstarks wrote:
             | No inline functions in library headers, then.
        
               | flohofwoe wrote:
               | Inline is mostly pointless in C anyway though.
               | 
               | But it might be a minor problem for STB-style header
               | libraries.
               | 
               | It's not uncommon for C++ projects to include the
               | implementation of an STB-style header into a C++ source
               | file instead of 'isolating' them in a C source file.
               | That's about the only reason why I still support the
               | common C/C++ subset in my C libraries.
        
         | pragma_x wrote:
         | I'm with you on this. The feature list reads like a subset of
         | later C++ standards that fit within C's (deliberately)
         | rudimentary feature set.
         | 
         | You could, in theory, just use C++ and be done with it. But
         | like any C++ project you'd need a pretty strict style guide or
         | even a linter, but this time it would have to be extra
         | restrictive lest you slide into full C++ territory. And maybe
         | that's a major stumbling block for some people?
        
         | BenjiWiebe wrote:
         | Won't any llvm/gcc supported target get the new version of C
         | automatically? You won't get it in the vendor-modified ancient
         | gcc toolchain for some other arch though.
        
       | musicale wrote:
       | I kind of like some of Metaware's high C extensions.
       | 
       | https://news.ycombinator.com/item?id=41647843
       | 
       | https://news.ycombinator.com/item?id=38938402
        
       | survivedurcode wrote:
       | Continuing to use a memory-unsafe language that has no recourse
       | for safety and is full of footguns and is frankly irresponsible
       | for the software profession. God help us all.
       | 
       | By the way, the US government did the profession no favors by
       | including C++ as a memory-unsafe language. It is possible to
       | write memory-safe C++, safe array dereferencing C++. But it's not
       | obvious how to do it. Herb Sutter is working on it with CppFront.
       | The point stands that C++ can be memory-safe code. If you make a
       | mistake, you might write some unsafe code in C++. But you can fix
       | that mistake and learn to avoid it.
       | 
       | When you write C, you are in the bad luck shitter. You have no
       | choice. You will write memory--unsafe code and hope you don't
       | fuck it up. You will hope that a refactor of your code doesn't
       | fuck it up.
       | 
       | Ah, C, so simple! You, only you, are responsible for handling
       | memory safely. Don't fuck it up, cadet. (Don't leave it all to
       | computers like a C++ developer would.)
       | 
       | Put C in the bin, where it belongs.
        
         | purple-leafy wrote:
         | Skill issue
        
           | pornel wrote:
           | It's been a skill issue for 40 years. How long are we going
           | to continue searching for those programmers who don't make
           | mistakes?
        
             | worksonmine wrote:
             | Programmers make stupid mistakes in the safest languages
             | too, even more so today when software is a career and not a
             | hobby. What does it matter if the memory allocation is safe
             | when the programmer exposes all user sessions to the
             | internet because reading Dockers' documentation is too much
             | work? Even Github did a variant of this with all their
             | resources.
        
               | pornel wrote:
               | Because memory vulnerabilities don't make programs immune
               | to other dumb mistakes. You get these vulnerabilities _on
               | top of_ everything else that can go wrong in a program.
               | 
               | Manual checking of memory management correctness takes
               | extra time and effort to review, debug, instrument, fuzz,
               | etc. things that the compiler could be checking
               | automatically and reliably. This misplaced effort wastes
               | resources and takes focus away from dealing with all the
               | other problems.
               | 
               | There's also a common line of thinking that that because
               | working in C is hard, C programmers must be smarter and
               | more diligent, so they wouldn't make dumb mistakes like
               | the easy-language programmers do. I don't like such
               | elitist view, but even if true, the better programmers
               | can allocate their smarts to something more productive
               | than expertise in programs corrupting themselves.
        
               | chongli wrote:
               | _Because memory vulnerabilities don 't make programs
               | immune to other dumb mistakes. You get these
               | vulnerabilities on top of everything else that can go
               | wrong in a program._
               | 
               | The issue is that these great new tools don't just fix
               | the old vulnerabilities, they also provide a lot of new,
               | powerful footguns for people to play with. They're
               | shipping 2000 feet of rope with every language when all
               | we need is 6 feet to hang ourselves.
        
               | pornel wrote:
               | There has been a bunch of failed C killers, and C++ has
               | massively shat the bed, so I understand that people are
               | jaded.
               | 
               | However, this pessimistic tradeoff is just not true in
               | case of Rust -- it has been focused from the start on
               | preventing footguns, and actually does a great job of it.
               | You don't trade one kind of failure for another, you
               | replace them with compilation errors, and they've even
               | invested a lot of effort into making these errors clear
               | and useful.
        
         | fjfaase wrote:
         | There are still applications (especially with embedded devices)
         | where you do not dynamically allocate memory or might not even
         | use pointers at all.
        
         | dxuh wrote:
         | You can't just put a language in the bin that has been used for
         | 50 years and that a huge percentage the present day software
         | infrastructure is built on.
         | 
         | I see comments like yours everywhere all the time and I
         | seriously think you have a very unhealthy emotional
         | relationship with this topic. You should not have that much
         | hate in your heart for a programming language that has served
         | us very well for many decades and still continues to do so.
         | Even if C was literally all bad (which imho isn't even
         | possible), you shouldn't be that angry at it.
        
         | uecker wrote:
         | There good tools that help improving memory safety in C and I
         | do not think Rust is a good language. Of course, the worst
         | about Rust are its fans.
        
       | tenderfault wrote:
       | any chance of getting a responsive TOC in any pdf reader
       | whatsoever?
        
         | kamaal wrote:
         | https://www.manning.com/books/modern-c-third-edition
        
       | johanvts wrote:
       | I payed for this on manning and they didn't even release the
       | final version yet. I guess I didn't understand what I was buying,
       | but I can't help feel a bit cheated.
        
       | sylware wrote:
       | I am worried where "official" C is going. Its syntax which is
       | already too complex and already does too much, but that would
       | require to "break" backward compatibility namely it would require
       | "porting". But since it would be still "C" that amount of work
       | should be close to "a bit" of "step by step" refactoring
       | 
       | For instance, only sized types:u8...s64, f32, f64... no implicit
       | casts except for void* and literals, no integer promotion, no
       | switch, no enum, only one loop keyword (loop{}!), no anonymous
       | code block, and no toxic attribute like "packed structure" which
       | makes us lose sight of data alignment... no _generic, typeof,
       | restrict, syntax based tls, etc...
       | 
       | But we would need explicit atomics, explicit memory barriers,
       | explicit unaligned memory access.
       | 
       | Instead of adding and complexifying C to make writing a naive
       | compiler more and more complex, long and a mouse and cat catchup
       | "to the standard" tedious task, what should be done is exactly
       | the other way around.
       | 
       | In end, I don't trust C officials anymore, I tend to stick to
       | C99, or even assembly (I am currently writing rv64 assembly I run
       | an x86_64).
        
         | eqvinox wrote:
         | > I tend to stick to C99,
         | 
         | > [...] But we would need explicit atomics, explicit memory
         | barriers, [...]
         | 
         | You should read a change summary before complaining about bits
         | missing from C99 that have in fact been added to C11.
         | 
         | > [...] no toxic attribute like "packed structure" which makes
         | us lose sight of data alignment [...]
         | 
         | And you should also familiarize yourself with what's in actual
         | ISO C vs. compiler extensions before complaining about bits
         | that are in fact compiler extensions.
        
           | sylware wrote:
           | I _TEND_ to stick to C99 = usually C99 with very few bits of
           | c11+ (usually the missing bits) and even some extensions
           | (often related to ELF /object format). But I really try hard
           | to minimize their usage.
           | 
           | The pb is in ISO c11+ we got some of the missing stuff for
           | modern hardware architecture, but also tons of tantrums
           | (_generic, typeof, restrict....)
        
       | eqvinox wrote:
       | > The storage order, the endianness, as given for my machine, is
       | called little-endian. A system that has high-order representation
       | digits first is called big-endian. Both orders are commonly used
       | by modern processor types. Some processors are even able to
       | switch between the two orders on the fly.
       | 
       | Calling big endian "commonly used by modern processor types" when
       | s390x is really the only one left is a bit of a stretch ;D
       | 
       | (Comments about everyone's favorite niche/dead BE architecture in
       | 3... 2... 1...)
        
         | ondra wrote:
         | MIPS is still quite alive in consumer networking hardware.
        
           | eqvinox wrote:
           | True - but at the same time, about half1 of it is mipsel,
           | i.e. in little-endian mode :). It's also in decline, AFAICS
           | there is very little new silicon development.
           | 
           | 1 on the OpenWRT table of hardware
        
           | tonetegeatinst wrote:
           | Learning MIPS assembly currently using mars and QtSpim.
           | 
           | Any recommended hardware I should use for bare metal
           | development messing around? Hopefully priced like a SBC like
           | the raspberry pi.
           | 
           | Want to move from making basic programs like adding, messing
           | with functions, etc and bring my MIPS assembly up to a real
           | hardware environment.
        
             | AntoniusBlock wrote:
             | Many routers use the MIPS ISA and they can be rooted to get
             | shell access. That's what I did with an old Netgear router,
             | which was like a very low spec SBC. If you have a PS2 lying
             | around, you could try that.
        
         | throwaway19972 wrote:
         | "Modern" doesn't mean "currently widespread".
        
           | eqvinox wrote:
           | Indeed, if it meant "currently widespread" there'd be a
           | stronger argument for Big Endian with a lot of MIPS and PPC
           | chugging away silently. But interpreting "modern" as recent
           | development, BE is close to gone.
        
             | throwaway19972 wrote:
             | Is there some end to this criticism or do you have some
             | stake in dismissing big endian architectures?
        
               | eqvinox wrote:
               | Uh, why so serious? I called it "a bit of a stretch ;D" -
               | there was a reason for that smiley. I'm well aware BE is
               | alive enough to be around.
               | 
               | If you can't live without knowing, sure, my stake in
               | dismissing big endian architectures is that I can't in
               | fact dismiss BE architectures because I have users on it.
               | And it's incredibly painful to test because while my
               | users have such hardware, actually buying a good _test
               | platform or CI system_ is close to impossible. (It ended
               | up being Freescale T4240-QDS devkits off eBay. Not a good
               | sign when the best system you can get is from a company
               | that doesn 't exist anymore.)
               | 
               | And at some point it's a question about network
               | protocols/encodings being designed to a "network byte
               | order" determined in the 80s to be big endian. When
               | almost everything is LE, maybe new protocols should just
               | stick with LE as well.
        
               | pm215 wrote:
               | To be fair to IBM, with s390x they do have a "community
               | cloud" programme where open source projects can apply to
               | get a Linux s390x VM to use for things like CI:
               | https://community.ibm.com/zsystems/l1cc/ . But yeah, BE
               | MIPS is super awkward because the target systems are all
               | embedded things that are bad dev/CI machines.
        
               | eqvinox wrote:
               | Stupidly enough, "my" software is a routing control plane
               | (FRRouting), so what I need to support are exactly those
               | embedded things. I'm not sure anyone uses FRRouting on a
               | s390x machine. But maybe we should go ask IBM anyway, a
               | BE system is a BE system...
               | 
               | qemu CPU emulation exists too, but that's painfully slow
               | for an actual CI run, and I'm not sure I trust it enough
               | with e.g. AF_NETLINK translation to use the "-user"
               | variant on top of an LE host rather than booting a full
               | Linux (or even BSD).
               | 
               | And in the very best case, proper testing would pit BE
               | and LE systems "against" each other; if I run tests on BE
               | against itself there's a good risk of mis-encodings on
               | send being mis-decoded back on receive and thus not
               | showing as breakage...
               | 
               | ... really, it's just a pain to deal with. Even the
               | beauty (in my eyes) of these T4240 ppc64 systems doesn't
               | bridge that :(
        
               | throwaway19972 wrote:
               | > there was a reason for that smiley
               | 
               | Fair--my bad, I can fail at reading tone sometimes.
               | 
               | Would you propose the C abstract machine abstracting away
               | endianness entirely as an alternative? My understanding
               | is that deprecating support for existing architectures is
               | discouraged to every practical extent.
        
               | eqvinox wrote:
               | Maybe we failed to communicate because our brains have
               | different endianness? :D
               | 
               | To be honest, I don't think this is a solvable problem.
               | (Changing the C machine concept doesn't do much if you
               | need to process network traffic that uses both (e.g. IP
               | packet [big endian] carrying protobuf [little endian]).
               | It's already _mostly_ a question of data ingress
               | /egress.)
               | 
               | What is solvable though is making sure people are
               | sufficiently aware. And the people who read a book like
               | "Modern C" are probably a very good target audience,
               | building low-level bindings and abstractions. They should
               | know that LE and BE are _technically_ a free-floating
               | design choice, but _practically_ the vast majority of
               | systems is LE now. But at the same time, yes, BE isn 't
               | extinct, and won't be any time soon... and it's left to
               | them to make their best possible design given their
               | environments.
        
         | umanwizard wrote:
         | Isn't Sparc big-endian?
        
           | zifpanachr23 wrote:
           | Is SPARC still seeing serious hardware development like
           | s390x? I know it's still around but I can't recall the last
           | time I heard of any new hardware.
        
         | unilynx wrote:
         | well in a way all processors commonly use them... as big-endian
         | is also the network byte order
        
           | flohofwoe wrote:
           | ...x86 CPUs actually have special mov instructions now to
           | load big endian data. Not sure since when though (on godbolt
           | it needs `-march=native`:
           | 
           | https://www.godbolt.org/z/bWfhGx7xh
           | 
           | ...without -march=native it's a mov and bswap (so not too bad
           | either).
        
             | cmovq wrote:
             | Looks like it was introduced with Haswell (2013). So it's
             | safe to use if you're also compiling with AVX2.
        
         | legends2k wrote:
         | Arm is bi-endian and is alive in most phones.
         | 
         | I agree with another GP's comment that modern doesn't mean
         | popular/widely used.
        
           | umanwizard wrote:
           | I really doubt any mainstream smartphone runs their Arm chip
           | in big-endian mode ever.
        
             | legends2k wrote:
             | That's besides the point. The book's author has a valid
             | point. Being pedantic should be applied at all levels if
             | you're going that route.
        
               | eqvinox wrote:
               | The problem about being pedantic is that you can choose
               | different directions to be pedantic in. My "direction" is
               | that code isn't written in a vacuum, it mixes with code
               | millions of other people wrote and runs on machines
               | millions of other people built. As such:
               | 
               | My concern isn't that the phrasing in the book is _wrong_
               | , and I have expressly not argued that. It's that it
               | presents the issue as having no further depth, and these
               | two choices as equivalent. They aren't. The "Some
               | processors are even able to switch between the two orders
               | on the fly." that follows makes it even worse, at least
               | to me it really sounds like you needn't give any care.
               | 
               | And the people reading this book are probably the people
               | who should be aware of more real-world background on
               | endianness, for the good of the next million of people
               | dealing with what they produced.
        
           | kelsey98765431 wrote:
           | MipsBE is very common in edge devices on many networks. You
           | may have 5 MipsBE devices in your home or office without
           | realizing. It's almost never an issue so nobody cares, but
           | they are common.
        
           | unscaled wrote:
           | The book does say "Both orders are commonly used by modern
           | processor types". I'd say this sentence is quite misleading,
           | since it would lead you to believe two falsehoods:
           | 
           | 1. That both byte orders are equally prevalent in the wild,
           | particularly in systems that are expected to run modern C
           | code.
           | 
           | 2. That both byte orders are equally likely to be found in
           | "modern" (new or updated) processor design.
           | 
           | It's not entirely incorrect, but a better phrasing could be
           | used to clarify that little-endian is the more modern and
           | common storage order, but you still cannot ignore big-endian.
        
             | 3836293648 wrote:
             | Don't a bunch of web protocols use big endian?
        
         | Keyframe wrote:
         | well, network byte order is a thing. Not a processor though.
        
       | uvas_pasas_per wrote:
       | I've been using modern C++ for a personal project (a language
       | interpreter) for the last year+. I constantly think of switching
       | to C, because of the mental burdens of C++, and because of the
       | problems with tooling (Visual Studio's IntelliSense still barely
       | works, because I use C++20 modules), and compile times get ugly
       | because of the way the language failures force so much into
       | interfaces (even with modules). But on the flip side I've gotten
       | so used to classes, member functions, generic programming
       | (templates), namespaces... I may be hooked.
        
         | fluoridation wrote:
         | I've been using C++ for the longest time, and I would never
         | give up destructors to switch to C.
         | 
         | For your particular use case, have you considered C#? VS works
         | much more nicely with it.
        
           | uvas_pasas_per wrote:
           | Yeah, I did. I want something low level and cross platform,
           | including mobile. I think when I tried the C# for iOS stuff,
           | nothing worked. But it's probably too much VM/runtime for me
           | anyway, for this project.
        
             | fluoridation wrote:
             | Fair enough. I would've used C++ as well.
        
             | neonsunset wrote:
             | iOS C# is more or less fine, there is quite a bit of work
             | done in .NET to make this better still. .NET 9 gains native
             | Swift Library Evolution ABI support even - you can
             | literally declare DllImports against public Swift APIs by
             | simply annotating them with [typeof(CallConvSwift)], it's
             | not as convenient as it sounds but it's only a matter of
             | time when the tools like
             | https://github.com/royalapplications/beyondnet adopt this.
             | It's going to get much better once MonoAOT is replaced with
             | NativeAOT for all major publish modes for iOS.
        
       | delduca wrote:
       | I wish auto in C was similar to auto in C++.
        
       | RantyDave wrote:
       | Wait, C programmers now put the star on the left hand side?
       | 
       | char* thing; // good
       | 
       | char *thing; // bad
       | 
       | This ... is awesome. As a C++ "native" I've always found the
       | "star on the right" thing to be really horribly confusing.
        
         | bloppe wrote:
         | Ofc this has always been an option. In my C heyday I used to
         | put a space on both sides of the star. It makes for a more
         | consistent syntax when you have multi layer pointers with const
         | at various layers. For example:
         | 
         | // mutable pointer to mutable data:
         | 
         | char * str;
         | 
         | // Immutable pointer to immutable data:
         | 
         | char const*const str;
         | 
         | // Mutable pointer to an immutable pointer to a mutable pointer
         | to immutable data:
         | 
         | char const**const* strs;
        
       ___________________________________________________________________
       (page generated 2024-10-16 23:01 UTC)