[HN Gopher] Modern C for C++ peeps (2019)
       ___________________________________________________________________
        
       Modern C for C++ peeps (2019)
        
       Author : AlexeyBrin
       Score  : 169 points
       Date   : 2023-01-07 12:47 UTC (10 hours ago)
        
 (HTM) web link (floooh.github.io)
 (TXT) w3m dump (floooh.github.io)
        
       | [deleted]
        
       | sfpotter wrote:
       | C's advantages over C++ don't have much to do with language
       | features. I don't know what the author was thinking here. Any
       | diehard C++ user is not going to be swayed by "fewer features".
       | Why not write an article that gets into the actual strengths of
       | C?
        
       | stareatgoats wrote:
       | Previous discussions:
       | 
       | https://news.ycombinator.com/item?id=27288145 (May 26, 2021 -- 94
       | points, 21 comments)
       | 
       | https://news.ycombinator.com/item?id=21093727 (September 27, 2019
       | -- 102 points, 16 comments)
        
       | epolanski wrote:
       | hijacking: What is a good reference to learn the minimal amount
       | of C++ to do codewars/leetcode, write minor programs?
       | 
       | Would prefer something that keeps with modern C++ if possible.
        
         | mhh__ wrote:
         | Read leetcode solutions.
         | 
         | If you can already code you'll pick it up in seconds.
        
         | jasinjames wrote:
         | I haven't used C++ very much, but I found "A Tour of C++" to be
         | a great introduction. It focuses on the core of C++ OOP
         | semantics and is written by Bjarne Stroustrup himself. I like
         | the book because it doesn't try to answer the question "which
         | feature is the canonical way to solve problem X", since from
         | what I understand, the "canonical" way to solve a problem in
         | C++ changes quite frequently as new features are added. It was
         | recommended to me by one of my professors a few years back for
         | being short and accessible to a person who is looking to learn
         | what makes C++ special, not how to program from scratch.
         | 
         | EDIT: Reading the page again, it looks like the book does have
         | some discussion of C++17 and C++20 features, but I focused on
         | the first few chapters which really nail down the whole copy
         | and move semantics feature as well as operator overloading.
        
         | Jtsummers wrote:
         | When I went on a Modern C++ bender a year or two ago I went
         | with Stroustrup's _A Tour of C++_ (2nd edition when I bought
         | it, 3rd is out now). It was a good read, covered a lot of
         | details quickly. The rest of my re-learning was using
         | cppreference and reading through the list of standard types and
         | functions /methods on them, trying to translate old (clunky,
         | often subtly broken) C++03 code into modern C++.
         | 
         | https://www.stroustrup.com/tour3.html
         | 
         | https://en.cppreference.com/w/
        
         | rramadass wrote:
         | "Minimal" and "C++" do not belong in the same sentence :-)
         | 
         | That said, _C++ Primer by Lippman, Lajoie, Moo_ is a good book
         | for beginners. I think the latest edition covers upto C++11.
         | You can then followup with _Modern C++ Cookbook by Marius
         | Bancila_ for newer features upto C++20.
        
         | 22SAS wrote:
         | I write low latency C++ for a living. Read "A Tour of C++",
         | focusing on the STL and the basics of the language, basics of
         | templates. Anything else related to STL, look at cppreference.
        
       | jstimpfle wrote:
       | C hasn't seen a lot of relevant changes since C89 as far as I'm
       | concerned. The most important for me is definitely declare-
       | anywhere (which had been widely supported prior to
       | standardization in C99), it removes most of the friction when
       | quickly trying out new stuff.
       | 
       | Here are some things I almost always do to improve ergonomics
       | (some of this is debatable and none of this should be exposed in
       | a library, at least prefix things)                   // common.h
       | #pragma once              #include <assert.h>         #include
       | <stddef.h>         #include <string.h>         #include
       | <stdint.h>         #include <inttypes.h>              #define
       | ARRAY_COUNT(a) ((int) (sizeof (a) / sizeof (a)[0]))
       | #define STRUCT(name) typedef struct name name; struct name
       | #define CONTAINER_OF(ptr, type, member) ((type *) ((char *) (ptr)
       | - offsetof(type, member)))         #define ALIGN(n)
       | __declspec(align(n))         #define CACHE_ALIGN ALIGN(64) //XXX
       | typedef uint8_t u8;         typedef uint16_t u16;         //...
       | 
       | Each .c file includes "common.h" as the first thing. Each .h file
       | has #pragma once at the beginning.
       | 
       | ARRAY_COUNT(a) is used to get the capacity of a C array without
       | using defines (which is brittle). Unfortunately it's imperfect
       | because code breaks when you change arrays to dynamically
       | allocated buffers and you forget to switch to using dynamic
       | capacity values. This is one case where I'd like to see some
       | standards update that allows us to improve safety.
       | 
       | STRUCT(x) is used to declare struct x with typedef -- I've grown
       | to hate tag namespaces for their boilerplate. I simply uppercase
       | types, and the problem (that struct tags were invented to solve)
       | is gone.
       | 
       | Probably I'll make something like this very soon to get started
       | static void *__xmalloc(size_t size);         static inline void
       | __xmalloc_array(size_t size, size_t count) { /* ... */ }
       | #define xmalloc(type) ((type *) __xmalloc(sizeof (type)))
       | #define xmalloc_array(type, count) ((type *)
       | __xmalloc_array(sizeof (type), (count)))
       | 
       | Later obviously more sophisticated allocation is needed, but the
       | point here is how macros can be employed to improve safety and
       | ergonomics. This is how I do polymorphism in C basically, not
       | much more is needed than abstracting over size and maybe
       | alignment for almost everything. In some cases, manually set up
       | v-tables make sense from an architectural perspective.
       | 
       | I also often make a very simple "logging" module that does
       | basically printf logging but with \n automatically added and
       | optionally printing out __FILE__ and __LINE__ but without
       | requiring to re-type this all the time. Something like
       | void __msgf(const char *file, int line, const char *fmt, ...)
       | {             va_list ap; va_start(ap, fmt);             if
       | (__debug) printf("At %s.%d: ", file, line);
       | vprintf(fmt, ap);             printf("\n");
       | va_end(ap);         }         #define msgf(...) __msgf(__FILE__,
       | __LINE__, __VA_ARGS__)
       | 
       | Other than that, I like to not think about the language but about
       | what the machine is going to do. How to decrease size of working
       | set and generally speed up the program. How to speed up
       | compilation (don't expose all the internals in the .h files).
       | Things like that.
       | 
       | There are very few container data structures actually needed,
       | most of the time it's just C-arrays, dynamically allocated
       | buffers (pointer + capacity), and some simple queues /
       | synchronization primitives. Also linked lists. A favorite of mine
       | are chunk-lists. All of these are very simple to implement --
       | there is hardly a point of making them in a super-general
       | library, it's ok to code them from scratch for all but the
       | smallest projects.
        
         | rramadass wrote:
         | Is there a reason for using leading double underscores for your
         | symbols when the standard says don't?
        
           | jstimpfle wrote:
           | (Added msgf macro wrapper in above comment).
           | 
           | I like it like that. It's also done like that in some
           | codebases that I'm often skimming, e.g. the Linux kernel. I
           | don't worry about the standard in this case. As I've written
           | more and more code, my willingness to do extra bullshit
           | chores when there is no practical reason has been steadily
           | decreasing.
           | 
           | Should you happen to choose an identifier that is already
           | used by the host environment, you'll notice and can fix it.
           | (It has literally _never_ happened to me).
           | 
           | The intention here is that the prefixed variants are
           | "private" i.e. they should not, or only rarely, be used
           | directly. Instead, I use the macro layers to automatically
           | fill in the right values and to improve safety. The above
           | allows me to write                   My_Foo *foo =
           | xmalloc(My_Foo);
           | 
           | As you can see, there is very little boilerplate. Prefixing
           | the implementation function makes it less likely to
           | accidentally use them when doing code completion. The prefix
           | nicely documents the relation between the macro and the
           | inline function, and I don't have to bother coming up with a
           | different name or naming scheme.
        
       | WalterBright wrote:
       | > Enable all warnings!
       | 
       | The trouble I've run into with that is different compilers have
       | different warnings, sometimes they are mutually contradictory, so
       | it is not possible to have portable code that doesn't trigger
       | warnings in one compiler or another.
       | 
       | I.e. the general problem with warnings is the language gets
       | balkanized into multiple competing and incompatible dialects.
       | 
       | What _should_ happen is to carefully examine the warnings, and
       | adopt the best into the language Standard as errors.
       | 
       | For example:                   if (a < b < c)
       | 
       | That is pretty much 100% a bug in the code. Just make it illegal
       | already. D does.
       | 
       | For another:                   for (i = 0; i < 10; ++i);
       | {             ...         }
       | 
       | Long ago, an expert C programmer came to me once and said he'd
       | spent all day trying to figure out why his loop only executed
       | once. I pointed to the semicolon. He sighed. I put a warning for
       | that in the compiler. I noticed that other compilers eventually
       | did, too.
       | 
       | In D, it's an error.
       | 
       | A third example:
       | 
       | In the Joint Fighter coding standard, it says don't use `l` as an
       | integer suffixe, as in `124l`, because in many fonts it looks
       | like `1241`. Solution: D does not allow `l` as a suffix. There's
       | is no reason to support that suffix. No reason to put it in
       | coding standard. Do the world a favor, just make it illegal.
       | Done!
        
         | ndesaulniers wrote:
         | > sometimes they are mutually contradictory
         | 
         | Right, there are definitely some C++ specific warnings for
         | different language compat rules (around std::move IIRC) that
         | are non-mutually-satisfyable.
         | 
         | This article recommends -Weverything w/ Clang. Clang developers
         | DO NOT ENDORSE the use of -Weverything.
        
         | itsJustTrivial wrote:
         | The third one must be satire and if not your support for it
         | must be
        
           | Jtsummers wrote:
           | It's not hard to check, the guidelines are posted online:
           | 
           | https://www.stroustrup.com/JSF-AV-rules.pdf#page17
           | 
           | AV Rule 14 is the one in question. Uppercase suffixes are
           | fine, lowercase is not, and the rationale is "Readability."
        
             | WalterBright wrote:
             | thank you for the link, I was too lazy to do the work
             | myself
        
         | johnisgood wrote:
         | Use proper fixed-width fonts! :P
        
           | roland35 wrote:
           | l'd th0ught you wou1d say that!
        
         | tialaramex wrote:
         | > (a < b < c)
         | 
         | On the other hand the intention (assuming numerical types) is
         | clear, if people keep doing this, what machine code is emitted
         | by common compilers for the "best" way to express this? And how
         | about for the idiomatic way to express it? Perhaps the insight,
         | if it's common, is that we should provide a nice way to do this
         | which emits efficient machine code.
         | 
         | Rust says "chained comparison" is forbidden, so it knows what
         | we're going for here, and indeed it suggests you might want (a
         | < b) && (b < c) which is how I'd write this, but of course the
         | opposite could be faster, as perhaps could
         | ((a+1)..c).contains(&b) and either may make more sense in
         | context of the usage.
        
       | JonChesterfield wrote:
       | Anyone know of a better blog/reference for why to use C over C++
       | today? I'm still writing some of my own stuff in C but honestly I
       | think that's inertia. Freestanding C++ with the exceptions turned
       | off would work fine.
        
         | rramadass wrote:
         | >why to use C over C++ today?
         | 
         | Simplicity. You can keep the entire language in your head and
         | everything is explicit with no "magic" involved.
         | 
         | That said, you might want to start moving to C++ slowly, first
         | as a "better C with user-defined types" and then to
         | OOP/Generic/etc. programming goodness which gives you a much
         | larger design space.
        
           | mhh__ wrote:
           | You can keep the language in your head, but there's good
           | empirical evidence you can't keep the code in your (companies
           | collective) head.
        
           | JonChesterfield wrote:
           | There's pretty much all the same compiler magic either way.
           | At least C++ gives you placement new and std::launder, C
           | gives you malloc and you cross your fingers that the
           | compiler's alias analysis goes your way for now.
        
           | WalterBright wrote:
           | > You can keep the entire language in your head
           | 
           | Explain, without looking it up, exactly how macro expansion
           | works.
           | 
           | (Don't feel bad. I've written 3 implementations of the CPP,
           | and still couldn't tell you how it works without carefully
           | reading it again.)
        
             | ndesaulniers wrote:
             | Right, you can "hide" what's going on in otherwise
             | proceedural C codebases w/ the preprocessor.
             | 
             | To be more charitable to OP, in C++ you can further "hide"
             | what's going on with destructors in addition to the
             | preprocessor.
             | 
             | (I like RAII and hope more C programmers can learn to
             | appreciate it. I have fixed and reviewed many fixes in the
             | Linux kernel for -Wsometime-initialized because error
             | handling using goto is a constant source of programmer
             | mistakes introducing UB).
             | 
             | I'd love to chat sometime about approaches to implementing
             | a CPP!
        
               | int_19h wrote:
               | It's not even that macros can hide complexity. It's that
               | the specification for the C preprocessor is itself rather
               | complicated and non-obvious - e.g. what happens when
               | nesting even simple macros, or even something as basic as
               | tokenization.
        
               | WalterBright wrote:
               | Right, I was talking about the specification, not the
               | use.
               | 
               | How people _use_ the preprocessor is by just fiddling
               | with it till it delivers what they want, then they move
               | on.
        
         | pjmlp wrote:
         | There are only two valid reasons, UNIX/POSIX kernels that
         | naturally will hardly embrace anything else, and embedded
         | toolchains that refuse to embrace modern times.
        
         | dataflow wrote:
         | I don't know of a reference, but C encourages to do more things
         | at run time, whereas C++ encourages you to do more things at
         | compile time. Simplest example of this is probably qsort() vs.
         | std::sort(), but it extends far beyond this. So you end up with
         | slightly slower code in C, but much faster compile times, and
         | much smaller binaries. So if you want smaller binaries or
         | faster compile times, you'll have an easier time keeping them
         | in C. Of course you'll have an easier time keeping &
         | introducing more bugs in C too, but that's another matter.
        
       | bgoated01 wrote:
       | I'm in a job where I was taught modern C++ from the beginning,
       | and I don't have the C89 familiarity that the author assumes
       | here. Anyone aware of a good resource to learn C coming from a
       | modern C++ background? I'm interested in learning it for hobby
       | embedded systems projects.
        
       | mrkeen wrote:
       | Comments, typedef wrappers, initialising structs?
       | 
       | C is great for what it is, but does this article really knock
       | down the "C is a subset of C++" argument?
        
         | rightbyte wrote:
         | "C is kinda a subset of C++" is close enought for any non
         | language lawyer.
         | 
         | The author names C99 feutures that has been in GNU C89 since
         | whenever.
         | 
         | I mean. C is whatever compiler you use. Some people just care
         | too much about what ISO says.
        
           | flohofwoe wrote:
           | In general I agree that the C standard isn't really relevant
           | for day to day work and that it matters more what compilers
           | actually implement, but a C library author needs to follow
           | the users, and that means at least supporting GCC, Clang and
           | MSVC (and the MSVC C frontend follows the C standard much
           | more strictly than GCC and Clang).
        
             | rightbyte wrote:
             | Ye I agree.
        
             | kevin_thibedeau wrote:
             | MS can pound sand. They ignored C99 for over a decade. It's
             | on them to support common extensions if they want to have a
             | viable C compiler.
        
       | justinlloyd wrote:
       | On the "no formal parameters function" - You can forward declare
       | the function with the parameter list, and then define the
       | function without a parameter list. This should (might) generate a
       | compiler error/warning in very old compilers, but really it is an
       | indicator that you are trying to use an older style of
       | declaration with "modern" convention.
       | 
       | If you have a forward declare you can then bring in different
       | headers (or use a pre-processor block) depending on need that
       | will redefine the function.
       | 
       | The reason this ability exists, in all the different incantations
       | you can use, is to ensure that very old-style C will still
       | compile.
       | 
       | You could also pop things off the stack manually inside a
       | function all the time, even without a paramter list, popping off
       | an "invisible" parameter also let you do things like return to a
       | different part of the code than the invoking function.
       | 
       | The compiler I worked on in 1985-ish supported forward
       | declarations, which was "fancy and new" at the time.
       | 
       | Insert obligatory, "I was there Gandalf, 3,000 years ago."
        
       | mgaunard wrote:
       | Most of the introduction is inaccurate. C++ does its best to
       | reintegrate all new features of C into C++ (not that C has
       | anything much going on since 1999), and a lot of efforts are made
       | to unify the languages. The only reason they're not unified is
       | because there are a few irreductibles that are against it, but as
       | written in the article, even Microsoft is in favour of just
       | having C++ and no C.
       | 
       | The creator of C++ himself is saying C++ is a better C and his
       | goal was for C to cease to be and be replaced. The reasons C is
       | not a strict subset of C++ is because some features of C are
       | outrightly dangerous and were changed in C++ for good reason.
       | 
       | Most of the new features of C these days are actually backported
       | from C++, such as atomics.
       | 
       | Also the whole thing about how in C you should think in terms of
       | modules rather than classes also applies to C++. OOP remains a
       | bad paradigm regardless of the language. And funnily enough some
       | of the biggest C frameworks are dedicated to providing OOP in C
       | (e.g. glib).
        
       | asveikau wrote:
       | > in C this function takes any number of arguments ... I guess
       | it's a leftover 'syntax pollution' from old K&R style function
       | declaration syntax). ... Instead in C, declare the parameter list
       | explicitely as 'void' so that you actually get compiler errors
       | when accidently passing arguments to 'my_func()' ...
       | 
       | I thought that C99 got rid of the "implicitly allows any number
       | of arguments" thing. Maybe I'm mistaken.
       | 
       | The place where I used to see this was header files that might be
       | used by a pre-ANSI compiler. They would omit arguments in the
       | header declarations of functions. I even remember some X11
       | headers putting those arguments in an ifdef, so that if you had a
       | decent compiler you'd get the checks, but it would still work on
       | ancient compilers.
       | 
       | It wasn't that the function bodies would have args missing and
       | somehow use them. It was about declarations, the kind you see in
       | header files.
        
         | flohofwoe wrote:
         | C23 finally gets rid of it, along with a couple of other warts
         | (like '= {0};' to zero-initialize a struct, instead '= {};' is
         | allowed now).
        
       | saboot wrote:
       | I'd like to ask HN for references explaining how to layout a
       | large program in C, for someone used to thinking with OOP. For
       | example for a moderately complex arcade game, how would I manage
       | all the states, entities, and interactions? I can write any
       | simple C program but this always trips me up.
        
         | huijzer wrote:
         | Basically just like you would in OOP. Put related variables
         | next to each other in a group and pass this group around. In
         | OOP this is called a class and in classless languages such as C
         | this is called a struct. The functions on this object can be
         | organised in modules just like would be done in OOP. See also
         | https://gamedev.stackexchange.com/a/172405.
        
       | IChooseY0u wrote:
       | What's the point of this? Just use an int.
       | 
       | typedef struct { int val; } meters_t;
        
         | [deleted]
        
         | 6equj5 wrote:
         | To me, the point of it is just to be more explicit and ease
         | maintenance, like `typedef int meters_t`, but more extensible.
         | Compare `int foo_len` to `meters_t foo_len`.
         | 
         | Explicitness: Consider the ambiguousness of `int foo_len` when
         | you're swapping between meters and feet (but use `int` for
         | both).
         | 
         | Ease maintenance: If you want to change the type you use to
         | represent meters (say from `int` to `size_t`, since it might be
         | wise to make it unsigned), compare going through the code and
         | changing each meter-related instance of `int` vs. just changing
         | the `meters_t` struct declaration.
         | 
         | More extensible: Compared to `typedef int meters_t`, using a
         | struct is useful if you ever have to add to the struct. (Maybe
         | a second numeric member representing the conversion to some
         | other unit of length or something.)
         | 
         | For "meters", this doesn't really apply, but using a struct
         | also prevents you from accidentally trying to do math with
         | numeric types that you shouldn't do math with (like ID numbers
         | or something): https://stackoverflow.com/a/18876104/9959012
         | 
         | Also, you probably shouldn't use `_t` at the end since that
         | should be reserved by the C standard and by POSIX:
         | https://stackoverflow.com/a/231807/9959012
        
       | zabzonk wrote:
       | > theoretically RAII is about general resource management, not
       | just about memory. But at least in my experience, it's always
       | about memory management
       | 
       | only if you ignore things like iostreams, thread locks, etc.
        
         | wayne wrote:
         | While not trivial, the problem of remembering to free
         | everything is slightly simpler when you don't have to worry
         | about exceptions.
        
           | otabdeveloper4 wrote:
           | Yes, just put a "if (errno) goto err" after every function
           | call in your program.
           | 
           | (That was sarcasm, FYI. Obviously it's 2023 and nobody wants
           | to do manual function calling boilerplate as if you're
           | programming 1960's assembly.)
        
             | d12bb wrote:
             | Well, to quote some Go..                 if err != nil {
             | return nil, err       }
        
             | WalterBright wrote:
             | I used to do `if (errno) goto err;` a lot. I eventually
             | realized that nested functions did the job much nicer:
             | void err() { printf("oops, we failed"); }         ...
             | if (errno) return err();
             | 
             | This cleaned up a lot of my code. (The optimizer would of
             | course inline the function, and the common tail merging
             | would automatically convert it to the goto version in the
             | optimizer. So this was cost-free.)
             | 
             | Why C doesn't officially have nested functions is, well,
             | that's C!
        
               | mhh__ wrote:
               | This is my one of my favourite patterns with nested
               | functions - clean code, and avoid a ratsnest of 70s basic
               | style GOTO.
               | 
               | It starts with one goto, then one more, then another.
               | 
               | The first goto is usually clever but then someone else
               | arrives and hacks another one because they don't want to
               | change the code too much.
        
               | int_19h wrote:
               | Cleanup goto-style is hard to describe as a "rat's nest",
               | since all the gotos in the same function go to the same
               | label at the end that just does all the cleanup before
               | returning. There's nothing clever about it, and many
               | codebases wrap it into macros and such to make the syntax
               | more concise and the intent explicit.
        
               | nextaccountic wrote:
               | > Why C doesn't officially have nested functions is,
               | well, that's C!
               | 
               | Maybe some day it will. It _should_ anyway
               | 
               | But GCC and Clang already support it, and while I can't
               | find if MSVC does, it probably does
               | 
               | edit: also, now I see that the goto in if (errno) goto
               | err is really just an (inlined) tail call!
        
               | ndesaulniers wrote:
               | Clang doesn't support the GNU C extension for nested
               | functions.
        
               | int_19h wrote:
               | MSVC does not support it.
               | 
               | In general, any random C compiler is likely to not
               | support that feature, because the way it interacts with
               | function pointers makes it unnecessarily complicated.
        
               | WalterBright wrote:
               | > unnecessarily complicated
               | 
               | Pascal could do it in the 1970s, and even Tiny Pascal
               | could do it, see the listing for the compiler in BYTE.
               | 
               | https://archive.org/details/byte-magazine-1978-09
               | 
               | As for function pointers, a simple solution is to not
               | allow them unless the function is declared `static`.
               | `static` functions don't have a hidden static link.
        
             | mypalmike wrote:
             | *for some definition of nobody
        
             | nextaccountic wrote:
             | I think Zig's defer and errdefer make this pattern slightly
             | more tolerable, but proper RAII is indeed best
             | 
             | https://ziglang.org/documentation/master/#defer
             | 
             | https://ziglang.org/documentation/master/#errdefer
        
           | pjmlp wrote:
           | setjmp/longjump/signals
        
           | jcelerier wrote:
           | but.. with RAII you don't either, that's the whole point
        
         | moloch-hai wrote:
         | Yeah, stopped reading after that. How wrong does he want to be?
         | 
         | It is hard to tell what the point is of this article is. _Yes_
         | , the C-ish subset of C++ differs from ISO C, in trivial
         | details. That is irrelevant to C++ users, who may consult ISO
         | C++ to discover the actual subset. They interact with _any_ C
         | only when they #include a header for a 3rd party C library, and
         | then the relevant subset is what, exactly, appears _in that
         | header_.
         | 
         | There is really no legitimate reason to code C anymore: A C++
         | compiler is available for any modern development target.
         | Confining yourself to any C subset (with or without ISO C
         | marginalia) amounts to crawling when you can fly.
         | 
         | To be more specific: nothing that can be done using C is
         | unavailable to the C++ coder, but enormous power is available
         | to the C++ coder and wholly unavailable in C.
        
           | jstimpfle wrote:
           | For me the legitimate reason to code C is to get stuff done,
           | period.
           | 
           | There is hardly a need for more (to do what I do -- systems
           | in the broadest sense) and there's a lot of time saved by not
           | thinking about 21 "safe" and "ergonomic" ways to write
           | everything in C++ (that very often end up being not that
           | safe, and unintelligible).
           | 
           | Yes, there is the "subset" argument, only use what you need.
           | But I don't buy that, I'm not like that, it doesn't work for
           | me. Constraining myself makes things easier.
        
             | glowingly wrote:
             | This somewhat mirrors my own development.
             | 
             | I started in C, and am still in it for systems/OS
             | development at work.
             | 
             | In UI development, we had older C toolkits, but are
             | replacing them with newer toolkits that are C++. I started
             | off trying to just do C business logic with some C++ glue
             | code for the UI. Sticking to what's familiar. In the end,
             | it was just easier to do everything properly. All C in
             | areas that are C. All C++ in areas that are C++. Same with
             | C#. Trying to mix them is just more work than necessary.
             | 
             | At least I get to add C++ to my resume. There is a little
             | joke at work, w.r.t. our existing codebases and code
             | choices. They are career-driven choices, not technical
             | choices. The result is a mess of a codebase, but I'm fully
             | embracing that now.
        
             | RcouF1uZ4gsC wrote:
             | > For me the legitimate reason to code C is to get stuff
             | done, period.
             | 
             | If I want to just get stuff done, C++ is great for that.
             | Don't underestimate how much you gain by having string,
             | vector, and map when you just want to crank out code.
             | 
             | Nobody is forcing you to overdesign stuff. You can always
             | just crank out the code you want.
        
               | dkersten wrote:
               | Yeah, just having the standard library container types
               | (or similar generic third party libraries if you don't
               | like std ones, eg I tend to use phmap maps and sets
               | instead of std::unordered_map/set). Templated containers
               | just make using them so much easier than generic
               | containers in C. Ergonomics matter when you just want to
               | get things done. Plus the C++ containers make it easier
               | to manage memory IMHO.
               | 
               | The main reason I still sometimes use C is compiler
               | support for microcontrollers where everything tends to
               | have a C compiler but not everything has a C++ one (or if
               | it does, not necessarily an up to date one).
               | 
               | I don't use either in my day to day really but I do have
               | sone personal projects. Ultimately I foresee doing these
               | in Rust where I can, but for stuff like hobbyist game
               | development, I'm currently too bought into some of the
               | C++ libraries, primarily EnTT, but even there I think
               | I'll eventually just end up using Rust with Bevy. For now
               | though, I actually quite enjoy using C++17 (and maybe 20
               | soon if I find the free time).
        
               | pjmlp wrote:
               | Even something like Turbo C++ for MS-DOS is preferable to
               | raw C, the only embedded toolchains that don't offer even
               | that are usually stuff like PIC and similar.
               | 
               | Which, in any case, have companies like Mikroe selling
               | Basic and Pascal compilers.
        
           | saghm wrote:
           | > There is really no legitimate reason to code C anymore: A
           | C++ compiler is available for any modern development target.
           | Confining yourself to any C subset (with or without ISO C
           | marginalia) amounts to crawling when you can fly. > > To be
           | more specific: nothing that can be done using C is
           | unavailable to the C++ coder, but enormous power is available
           | to the C++ coder and wholly unavailable in C.
           | 
           | As a counterargument, sometimes constraints can be valuable
           | in their own right rather than just being requirements for
           | some larger goal. The most prominent example of this in
           | programming design is type checking; enforcing type checking
           | at compile time strictly reduces the number of programs you
           | can express, so dynamic languages can do everything that
           | static languages and more, and yet it turns out that it's
           | still pretty useful specifically because we sometimes want to
           | avoid the programs that a dynamic language would allow but a
           | static language would reject. At the risk of getting too
           | philosophical, imagine framing programming as an exercise in
           | trying to write rules to specify one program out of the set
           | of all possible programs, where each bug you write that
           | changes the semantics moves one step further from your goal.
           | The compiler can act as a "filter" on the set of programs you
           | can potentially select by refusing to process your rules if
           | they would specify one of the forbidden programs, which
           | reduces the chance selecting the wrong program by mistake. In
           | this scenario, the theoretically ideal compiler would be the
           | one that rejects all programs other than the exact one you
           | want! This obviously isn't possible in the general case, but
           | the point here is that as long as compilers don't reject the
           | exact program you want, a compiler that rejects more programs
           | is _more_ useful.
           | 
           | I'm not making any claim about whether it is in fact worth it
           | to code C anymore, but I disagree with the logic that C++
           | letting you do anything C can do and more is proof that C++
           | should always be used over C. Maybe C is crawling compared to
           | C++'s flying, but if my goal is to get a pen that falls under
           | my desk, it's a lot more useful for me to not use an airplane
           | to do it.
        
             | moloch-hai wrote:
             | Getting your pen doesn't demand _enormous power_ , but you
             | are not obliged to field any, either way.
             | 
             | We generally don't know, when we set out, how much support
             | we will be able to benefit from, down the road.
        
               | galangalalgol wrote:
               | I think the gp was talking about anti-features. Austral's
               | announcement included a list of features it avoided on
               | purpose. Including several from c++. operator
               | overloading, increment operators and even implicit oder
               | of operations were listed as features that made codebases
               | worse, even if no one has to use them.
        
               | moloch-hai wrote:
               | Coding by superstition was a bad idea before Austral, and
               | remains a bad idea after.
        
           | phoehne wrote:
           | Anyone who says C is not important hasn't worked on embedded
           | systems or operating systems.
           | 
           | I work on embedded systems. There are roughly 3 choices for
           | languages[1]. C, Ada, and C++. Except the version of C++ is
           | sometimes often vendor specific and may lack features that
           | are "standard C++", and never have a "full" STL. In addition,
           | if you're not super careful with C++, what you thought was a
           | copy or declaration can execute additional constructor or
           | assignment logic. It might just interfere with your estimate
           | of how long something can take (which is super important in
           | interrupt handlers), or worst case, smash your stack (which
           | can be as small as 1-2k). With C, you pretty much know what's
           | going to happen and what's getting called. The fact that C
           | doesn't require a runtime (nor does Ada), means you can use
           | it to write kernels. You can't do the same with a 'full'
           | version of C++, so you're back to a cut down C++.
           | 
           | I also spend time debugging compiled, optimized code by
           | tracing instructions. This is hard enough in C, where I don't
           | magically jump to a constructor function or an assignment
           | function. I can visualize in my head how the C code could
           | look like, given the instructions the optimizer produced. So
           | it may not look like my code, but it is a version of the code
           | that I can at least logically infer.
           | 
           | Ada is nice, but outside of some super-safety critical
           | realms, it hasn't had the uptake. Private industry prefers
           | not having to make the investment and the government let
           | everyone waive out of it.
           | 
           | 1. I know people are going to to say 'what about Rust, Go, or
           | embedded something or other?' They are not as mature in their
           | development lifecycle. So there's no validated RTOS for Rust
           | (there are some experimental ones). Go is even further
           | behind. Basically, you need a vendor supported RTOS that's
           | validated for the safety and operational requirements of your
           | use case, before you can be a serious choice for a lot of
           | embedded work.
        
             | groos wrote:
             | I'm not an Ada expert but I suspect any exception
             | implementation would require a runtime of sorts.
        
               | phoehne wrote:
               | It's been decades since I worked with Ada, so I could be
               | wrong, but you could build it with an embedded runtime
               | that allows you to do tasking and (I think I remember)
               | exceptions. But it also has a language standard way to
               | create limit certain features. The difference is the C++
               | is a grab bag, depending on the vendor, platform, RTOS,
               | etc.
        
             | moloch-hai wrote:
             | If you are using vendors' proprietary compilers in place of
             | Gcc, it is purely masochism.
        
               | phoehne wrote:
               | Some vendors base their compilers on GCC or Clang. But if
               | you're not using their provided compilers, you either
               | need to 1) be willing to shoulder the expense of making
               | changes to GCC or Clang for whatever you're adding to the
               | silicon in terms of instructions, and 2) be willing to
               | get your combination of RTOS and compiler re-validated
               | for the safety or operational standard on which you need
               | to deliver.
               | 
               | But even if you do use GCC or Clang, it doesn't change
               | the mechanics of C++ as a poor language for embedded or
               | operating system work. It just means you have the same
               | choices (e.g. limited support for containers and strings,
               | no exceptions, limited smart pointers, etc.) and you're
               | making that choice based on GCC or Clang's limits.
        
               | moloch-hai wrote:
               | Maybe you should explain to Andreas how C++ was such a
               | bad choice for SerenityOS. Or to authors of the various
               | RTOSes coded in it.
        
               | phoehne wrote:
               | It's not impossible. Other people have done it. Some have
               | done it with a minimal kernel or micro-kernel in C and
               | then services on top of that in C++. Just doing a quick
               | perusal of their code base, the low level stuff is
               | functions and structs. So, yes, it is a .cpp but not that
               | different from the code you'd write if it were a .c. And
               | it appears they're turning off exceptions and the
               | standard library (which is completely reasonable). They
               | probably have some additional coding standard
               | (internally) like you can't use anything with a
               | constructor in certain contexts, etc.
               | 
               | And on a validated RTOS - it might be in C++ under the
               | covers. The examples that come to my mind aren't. But the
               | world is a big and magical place, so I can't say for
               | certain on every RTOS. The F35 uses C++ and a set of very
               | restrictive internal coding standards built on
               | https://en.wikipedia.org/wiki/MISRA_C.
               | 
               | And no one will die if their copy of Serenity OS crashes.
        
               | pjmlp wrote:
               | If it is compiled by a C++ compiler, it is C++ regardless
               | of the language features being used.
               | 
               | Regarding language guides, C doesn't stop being C, even
               | when castrated via MISRA-C.
        
               | Jtsummers wrote:
               | Vendor compilers are common, if not typical, for embedded
               | and safety-critical systems. It's not masochism since
               | they work, and in my experience they work quickly to
               | address any compiler bugs you may discover and report.
               | This is also true for Ada, C++, and Fortran in that
               | domain.
        
               | abrawill wrote:
               | If the hardware is exotic I guess you'd have no choice.
               | But for security critical don't you run the risk of
               | relying on obscurity rather than security due to the
               | niche-ness of your stack?
               | 
               | What does a vendor compiler do or do better than a
               | compatible generic one?
        
               | Jtsummers wrote:
               | When you get a critical system certified for fielding you
               | aren't just certifying the source code, but the actual
               | executable and build process and test process and other
               | things. This requires reproducibility for years to come.
               | Choosing generic compilers may work in dev and parts of
               | test, but not for actual deployment as a consequence (or
               | it doesn't work well). Suppose you picked clang 11
               | several years back. Now you need to do an update to the
               | system, you can still use clang 11, but not clang 14 at
               | least not without doing a comprehensive recertification
               | process. Also, if an issue is discovered in clang 11 it's
               | likely been fixed in clang 14, but again you have to get
               | your system recertified with clang 14. And that's _if_
               | the issue is fixed, it may still exist.
               | 
               | With a vendor supplied compiler you can say, "We're using
               | version 11.2". A year or two later an issue is
               | discovered, the vendor _will_ backport a fix to 11.2
               | giving you 11.2.1 which is much less effort for
               | recertification. You aren 't depending on the kindness of
               | strangers (a terrible strategy) because you're actually
               | paying someone to do the work.
        
             | pjmlp wrote:
             | I love how usually not having support for full ISO C++ or
             | it not being up to date is an issue, while having to deal
             | with a cut down version of C, freestanding, a custom
             | library and compiler extensions is a plus.
        
             | kllrnohj wrote:
             | > Anyone who says C is not important hasn't worked on [..]
             | operating systems.
             | 
             | Most OS code is written in C++ or Object-C, not C. Between
             | Android, MacOS/iOS, and Windows there's not a lot of C
             | code. The kernels are most of it. The NT kernel has C++,
             | but hard to get a source on how much is still C or not. But
             | the kernel is _far_ from being most of the OS regardless.
             | 
             | > The fact that C doesn't require a runtime (nor does Ada),
             | means you can use it to write kernels. You can't do the
             | same with a 'full' version of C++, so you're back to a cut
             | down C++.
             | 
             | For a huge number of users, "full" C++ is C++ with -fno-
             | exceptions and -fno-rtti anyway, at which point the
             | 'runtime' is like 2 functions and it's absolutely perfectly
             | fine to use in a kernel. But regardless neither of those
             | features are inherently incompatible with being in a
             | kernel. You just have to implement a runtime to do that,
             | just like kernels written in C have to implement a libc
             | replacement.
             | 
             | Unless you're talking about the standard library (even
             | though nearly all of it is completely kernel-compatible out
             | of the box), but then you'd have to include libc & friends
             | in the "C runtime" category and then it's equally
             | impossible to use in a kernel.
        
               | phoehne wrote:
               | Sorry, I didn't mean the userland stuff above the kernel,
               | which, along with the boot loader, is the part that is
               | really restricted in any sense. With the standard
               | library, there are large parts that are not kernel safe.
               | That's because they rely on services like memory
               | allocation, which are different in the kernel. Or they
               | rely on kernel services, like files. And what I work on
               | has no kernel, just an RTOS.
               | 
               | And C in the kernel isn't really a libc replacement. They
               | often have different behaviors because they need to run
               | without allocating memory, or be interrupt safe. For
               | example, in some situations I use a function call
               | instruction that specifically does not creat a new stack
               | frame.
        
           | [deleted]
        
             | [deleted]
        
           | tejohnso wrote:
           | > There is really no legitimate reason to code C anymore
           | 
           | That's an interesting statement. Reminds me of statements
           | indicating any new code base should be started in rust over
           | c++.
        
             | pjmlp wrote:
             | The rewrite in C++ movement was lived in Usenet flamewars,
             | and we were in good path with all the userspace C++
             | frameworks in OS/2, MS-DOS, Windows, Apple and BeOS until
             | the GNU Manifesto came around and urged everyone to write
             | FOSS code in C.
             | 
             | "Using a language other than C is like using a non-standard
             | feature: it will cause trouble for users. Even if GCC
             | supports the other language, users may find it inconvenient
             | to have to install the compiler for that other language in
             | order to build your program. So please write in C."
             | 
             | GNU Coding Standard in 1994,
             | http://web.mit.edu/gnu/doc/html/standards_7.html#SEC12
        
               | moloch-hai wrote:
               | Stuff written about C++ before there was an ISO Standard
               | obviously does not apply after. And, we should know by
               | now how much Richard Stallman's prejudices are worth.
        
               | pjmlp wrote:
               | By all means, GNU Coding standards from 2006, 8 years
               | after C++98.
               | 
               | "When you want to use a language that gets compiled and
               | runs at high speed, the best language to use is C. Using
               | another language is like using a non-standard feature: it
               | will cause trouble for users. Even if GCC supports the
               | other language, users may find it inconvenient to have to
               | install the compiler for that other language in order to
               | build your program. For example, if you write your
               | program in C++, people will have to install the GNU C++
               | compiler in order to compile your program.
               | 
               | C has one other advantage over C++ and other compiled
               | languages: more people know C, so more people will find
               | it easy to read and modify the program if it is written
               | in C.
               | 
               | So in general it is much better to use C, rather than the
               | comparable alternatives."
               | 
               | http://gnu.ist.utl.pt/prep/standards/html_node/Source-
               | Langua...
               | 
               | As project lead, his opinions always mattered for what
               | goes into GNU.
               | 
               | What the rustaceans are doing nowadays I lived as C++
               | fanboy, so I am pretty aware how things went.
               | 
               | Remember that besides the license issue, another big
               | reason why GNOME came to be was because KDE uses C++,
               | hence the whole GObject stuff in C.
        
               | moloch-hai wrote:
               | You emphasize my point so clearly, I need add nothing.
               | 
               | But I will note that GNU Gcc and Gdb are both C++
               | projects now. Gold, the current GNU linker, started out
               | C++. Are there any other still-relevant GNU projects?
        
               | pjmlp wrote:
               | That is now, we were talking about when GNU/Linux started
               | to be relevant and what triggered language choice, versus
               | what was happening in the desktop PC world.
               | 
               | The latest version of the GNU coding standard is much
               | more permissive.
        
             | moloch-hai wrote:
             | Rust can still fizzle, like Ruby has.
             | 
             | It is still far from clear whether anybody will be learning
             | Rust, in ten years, instead of whatever is the new hotness
             | then. Hiring a domain expert who also knows Rust is
             | _generally impossible_ today, and will be for, at least, a
             | long time. So, starting a new project in Rust is OK for a
             | project you know won 't matter, but absurdly risky for one
             | that will.
             | 
             | C++ is mature. None of the above is a concern, for C++.
        
               | int_19h wrote:
               | In terms of adoption by _pre-existing large players_ in
               | the industry, Rust is already ahead of where Ruby was at
               | its hype peak. It 's just not quite as shiny because that
               | code is not running some web app that is immediately
               | demo-able, but some boring OS infrastructure stuff.
               | 
               | Given the money already invested into internal tooling
               | and infrastructure specifically for Rust, I just don't
               | see those large companies suddenly dropping it.
               | Regardless of how good the language itself is, the sunk
               | cost alone makes it hard to turn ship. And the language
               | _is_ good, so it 'll use that time to entrench itself
               | further.
        
               | AgentOrange1234 wrote:
               | Hrmn. Hiring rust experts may be hard, but the
               | opportunity to learn rust on the job could be appealing
               | to a lot of devs who want to grow?
        
           | KingLancelot wrote:
           | [dead]
        
         | TillE wrote:
         | Yeah, RAII helps you manage any type of OS handle with minimal
         | effort.
         | 
         | I can grab a mutex with std::unique_lock and not have to worry
         | about releasing it manually, which means simpler code and fewer
         | bugs.
        
           | jstimpfle wrote:
           | This is a mostly theoretical argument in my experience. It's
           | quite easy to close the things that you need to close (if
           | there are very many, something is wrong).
           | 
           | RAII can improve "scripting" speed when putting a lot of
           | automatic variables on the stack. However, a lot of those
           | variables need to be moved to the heap when proofing out the
           | code, and that consolidation isn't going well because RAII
           | doesn't work with generic code (void-pointers, memcpy etc).
           | You need to go all-in with RAII containers. Which causes a
           | lot of boilerplate and increases compilation times.
        
             | moloch-hai wrote:
             | You appear to be using "generic" to mean "C-ish". That is
             | not what the word means, in context, and there is no reason
             | to do any of it anyway. "Proofing the code"? Does that mean
             | anything at all?
             | 
             | For RAII on heap objects, you have std::unique_ptr, and no
             | boilerplate. Anyone failing to lean into RAII to manage
             | resources is just choosing to write unreliable code. Or C,
             | but I repeat myself.
        
               | mgaunard wrote:
               | Due to language limitations, a lot of C programmers make
               | all their variables shared pointers, which means they
               | don't have to figure out how to do value semantics,
               | moving and copying objects, especially in multithreaded
               | environments.
               | 
               | Interestingly, it seems Rust programmers do the same too,
               | because otherwise programming in Rust is too hard.
        
               | secondcoming wrote:
               | I assume you mean C++ here?
               | 
               | In any case, in my experience this bad practice comes
               | from Java devs who've moved to C++.
        
               | moloch-hai wrote:
               | Java Disease is its own topic.
               | 
               | But most shared pointer ownership in C is wholly _ad hoc_
               | , without even reference counting.
        
               | mgaunard wrote:
               | No, I mean C. People don't do that in C++, since C++
               | provides ways to define value semantics (copy, move,
               | assignment).
        
               | jstimpfle wrote:
               | > Due to language limitations, a lot of C programmers
               | make all their variables shared pointers, which means
               | they don't have to figure out how to do value semantics,
               | 
               | I wouldn't approve. Make shared (i.e. ref-counted)
               | pointers when actually needed. Writing C is best when not
               | mimicking bigger languages (i.e. with GC), but when
               | figuring out how a system can work optimally.
        
               | mgaunard wrote:
               | You might not approve, but the reality is that most C
               | code is built like that.
        
               | tialaramex wrote:
               | Your theory is that Rust programmers "make all their
               | variables shared pointers" ?
               | 
               | Even if I generously assume you're thinking of C++
               | "shared pointers", and so you've concluded that Rust's
               | Rc<T> (a reference counted T) is basically the same
               | thing, I don't see where you'd come to the conclusion
               | that Rust programmers do this for "all their variables"
               | as this seems wildly unlikely.
        
               | mgaunard wrote:
               | Just look at the code in cargo. (A)rc is all over the
               | place.
               | 
               | It's essentially a cop out to not have to think about
               | ownership and lifetime, and not have to worry about the
               | borrow checker.
               | 
               | The fact that this is so pervasive throughout Rust code
               | is for me a sign that Rust failed to deliver on its
               | initial promise. The borrow checker was meant to be its
               | main value added. But heh, people still find value added
               | to Rust otherwise.
        
               | tialaramex wrote:
               | Hmm. So the code I happened to have open was Aria's
               | "Cargo mommy" which has neither Arc nor Rc anywhere, but
               | is only a toy.
               | 
               | So I did go look at Cargo itself, but unlike you although
               | I do see some use of Rc and fewer of Arc it was scarcely
               | "all over the place" when I looked and in the cases I
               | spent any time actually thinking about it's a shared
               | value, so, yeah, that makes sense.
               | 
               | I randomly looked at cargo/core/profiles.rs and
               | cargo/core/registry.rs and
               | cargo/core/compiler/compilation.rs without finding either
               | Rc or Arc used in those files at all. Searching across
               | the whole repository I found some places which do use Rc,
               | and fewer using Arc (implying this data is shared between
               | threads) -- but this doesn't really support your original
               | claim does it?
        
               | mgaunard wrote:
               | I never meant cargo itself.
        
               | jstimpfle wrote:
               | > "Proofing the code"? Does that mean anything at all?
               | 
               | Since you're already schooling me on the meaning of
               | terms, you probably have enough experience to have
               | realized that the typical code begins life in a stubbed
               | out form that is barely functional enough to deliver
               | first results. It is then iteratively enhanced and made
               | more widely applicable, more robust, more refined, better
               | specified, more performant, and so on. In the process,
               | the code is undergoing many revisions, and moving data
               | structures from automatic storage (stack) to the heap is
               | very typical.
               | 
               | Another word I'm thinking of for this process is
               | "consolidation", which I have also used. My apologies if
               | I don't speak in your terms / in the most precise terms.
        
               | moloch-hai wrote:
               | I don't have experience of this "moving from ... stack to
               | the heap", in C++. Correct code is the same wherever the
               | objects live, whether stack, member, or heap. Changing it
               | costs time and adds faults.
               | 
               | This proofing process is another example of what makes
               | coding C more costly and buggy than coding C++. I did it
               | for years, and miss it not at all.
        
             | EliRivers wrote:
             | "It's quite easy to close the things that you need to
             | close"
             | 
             | Very true. It is easy. Not difficult. That doesn't seem to
             | stop enormous amounts of C++ code being created in which
             | this is simply got wrong. Just because it's easy doesn't
             | mean that huge numbers of programmers won't still get it
             | wrong. They do. Regularly. If they adopt RAII conventions,
             | they screw it up far less frequently. Those are the facts.
        
               | jstimpfle wrote:
               | I prefer reducing complexity instead of hiding it in
               | language cleverness. (Because hiding complexity doesn't
               | make it go away). Try adopting ZII (Zero is
               | initialization) and context managers (pooling, chunking,
               | freeing in batches etc.), this can reduce the amount of
               | boilerplate to a minimum. Writing generic code like that
               | (down to the binary level) also helps improving many
               | other metrics like executable size etc.
        
               | EliRivers wrote:
               | That's nice, and I'm sure your code is just lovely, but
               | there's just one of you and millions of people writing
               | C++ who can't do that but can just about manage some
               | simple RAII.
        
               | kllrnohj wrote:
               | > Because hiding complexity doesn't make it go away). Try
               | adopting [..] context managers (pooling, chunking,
               | freeing in batches etc.), this can reduce the amount of
               | boilerplate to a minimum
               | 
               | "don't hide complexity in small, single-purpose,
               | composable containers, instead hide complexity in hulking
               | behemoths which can consume months of refactorings &
               | iterations" ?
               | 
               | That's not simplifying your code to be generic. That's
               | adopting a particular framework & coding style. Which
               | yes, you have to do in C, but you're doing that _because
               | of_ language issues. You 're not saving yourself from
               | "language cleverness"
        
         | cjfd wrote:
         | One can do lots of things with RAII. I once wrote some classes
         | to generate xml. I used constructors to write opening tags and
         | destructors to write the corresponding closing tags....
        
       | travisgriggs wrote:
       | I do a bit of embedded C, 32kB for combined heap and stack, that
       | sort of thing. I wonder if the article author heralds from the
       | same space. I pretty much found myself going yup yup yup for most
       | of this. But I also know that when I move into higher
       | powered/level domains, I don't keep using C.
        
       | Warwolt wrote:
       | A very good article. I think it's nice as a C++ dev to see how
       | problems can be solved within the restraints of C, and how we
       | might learn from those limitations where C++ might have strengths
       | and where we might wanna lean on simpler C mechanisms.
        
       | codeflo wrote:
       | I appreciate the intent, C has become its own thing with its own
       | set of conventions, and people still writing "C/C++" often reveal
       | that they don't have a clue about either.
       | 
       | I think many C programmers might disagree with large parts of
       | this, however. Some things that caught my eye that might be
       | controversial:
       | 
       | > Wrap your structs in a typedef
       | 
       | The only reason to do this that's mentioned is "annoyance", which
       | I think is a weak reason if you have the kind of problem for
       | which you're considering to use C. The author already mentions
       | that it has a weird interaction with forward declarations. It's
       | my understanding that many C codebases don't do this, simply to
       | be more explicit about what's a struct and what's not, and to
       | avoid typedef explosion.
       | 
       | > the POSIX standard reserves the '_t' postfix for its own
       | typenames to prevent collisions with user types - make of that
       | what you will ;)
       | 
       | IMO, that's not something to casually dismiss with a smiley. If
       | it's reserved, don't do it.
       | 
       | > Typedef only creates a weak type alias not a proper new type
       | (it's really not much better than a preprocessor define), meaning
       | there's no warning when assigning to a different type from the
       | same base type
       | 
       | The point is somewhat valid, but the author seems a bit confused
       | about terminology here. It's not "a different type from the same
       | base type", instead, it's a different name for the _same_ type.
       | The comparison with the preprocessor is unwarranted.
       | 
       | > Be (somewhat) afraid of pointers
       | 
       | Why? The following rant about RAII doesn't really give that many
       | clues. Handles can have a performance penalty vs pointers -- or
       | be a performance benefit, this really depends on the details.
       | 
       | Also, this is where it might have been helpful to go into
       | different conventions about allocations, whether the caller or
       | callee should allocate, etc.
        
         | WalterBright wrote:
         | > It's not "a different type from the same base type", instead,
         | it's a different name for the same type.
         | 
         | This is why after much consideration, D uses `alias` instead of
         | `typedef`. `alias` works for other things, too, like creating
         | an alias for a symbol:                   alias sqrt =
         | math.std.sqrt;         x = sqrt(y);
         | 
         | or when you're sick of Java style names:
         | alias eggs = CookAndEatEggsForBreakfast;         eggs();
         | 
         | Of course, in C you'd use a macro for this. But C macros do not
         | respect scoping, so using them is akin to using a table saw
         | without a face shield.
        
         | rightbyte wrote:
         | > people still writing "C/C++" often reveal that they don't
         | have a clue about either.
         | 
         | How about "Java/Groovy" or "C#/VB"? C++ and C are related
         | enought to write that I think. Especially since it is common to
         | mix the languages in projects.
        
           | tialaramex wrote:
           | I think the things that particularly gets people angry are:
           | 
           | - When someone says "C/C++ Programmer" but they actually mean
           | either a C programmer, or a C++ programmer, and hey, it's all
           | the same right ? This is obviously related to the general
           | cluelessness of recruiters. The 19 year old wide boy
           | recruiting people for your bank will try to bring in an
           | experienced embedded C programmer to interview for a post on
           | your 5MLOC C++ line of business application, and they also
           | aren't clear on the difference between Mystic Meg (an
           | Astrologer) and Edwin Hubble (an Astronomer). It's also
           | infuriating when people who supposedly know either language
           | describe themselves this way. If you _actually_ know C or C++
           | fairly well, you should _know_ they 're different in
           | important ways and it's unlikely you actually know both
           | equally well.
           | 
           | - When a project/app/whatever is described as written in
           | "C/C++" but almost always this means either, "It's actually
           | C++ but a few files are technically not using any features
           | exclusive to C++ so I guess a C programmer could understand
           | them" (that's just C++) or "It's actually C, but we did
           | compile it with g++ and that worked".
           | 
           | If we saw Firefox referred to routinely as being
           | "C/Javascript/C++/Rust/Python/Assembler" then I don't think
           | you'd see as big a reaction to C/C++ but people will act as
           | though "obviously" somehow the C and C++ in Firefox are
           | basically interchangeable, but the Python and Javascript are
           | not.
        
           | nextaccountic wrote:
           | I think you're right. For example, I think Android apps
           | written in Java/Kotlin are better than apps written in web
           | technologies
        
         | flohofwoe wrote:
         | > Also, this is where it might have been helpful to go into
         | different conventions about allocations, whether the caller or
         | callee should allocate, etc.
         | 
         | There's another blog post about memory management which might
         | have aged a bit better (at least it's less controversial heh):
         | 
         | https://floooh.github.io/2018/06/17/handles-vs-pointers.html
        
         | pjmlp wrote:
         | People bashing about C/C++ don't have any clue about English
         | grammar rules.
        
           | lvass wrote:
           | Elaborate.
        
             | pjmlp wrote:
             | Any reference to an English grammar will easily explain
             | that / is an abbreviation for _or_.
             | 
             | "The Proper Use of the Forward Slash in English"
             | 
             | https://eslgrammar.org/forward-slash
             | 
             | Thus C/C++ as in _C or C++_.
             | 
             | On top of revealing lack of grammar knowledge, it also
             | reveals lack of understanding of ISO papers, documentation
             | from all companies with seat at ISO, their job boards, and
             | major publications like the now gone "The C/C++ Users
             | Journal".
        
               | codeflo wrote:
               | English grammar is a red herring in this discussion. You
               | would never claim that you bought a Ford/Toyota, or ate a
               | pizza/banana.
        
               | pjmlp wrote:
               | Depends on the quality of speech.
        
               | tialaramex wrote:
               | Grice's Maxims apply. In many cases it's weird to have
               | the "or" where we're otherwise very specific.
               | 
               | "Wanna go get a burger/pasty ?" seems pretty reasonable
               | if we happen to live somewhere that a burger or a pasty
               | are reasonable food alternatives. Maybe there's a burger
               | van parked on the corner, and there's also a shop that
               | sells pasties. Pizza is food, but it's not an option
               | here.
               | 
               | "Wanna buy a house/chocolate bar ?" seems very weird. Why
               | these specific alternatives? Do you live somewhere with a
               | confectioner and a real estate business and nothing else?
               | Even so, what sort of person isn't sure which of these
               | things they would buy ?
               | 
               | C/C++ is closer to the latter the more you know about
               | both languages.
        
             | colejohnson66 wrote:
             | I think they're referencing how English grammar isn't
             | consistent, but at least C and C++ have a defined grammar
             | (from the spec). Therefore, people bashing C and C++ should
             | look in the mirror?
        
               | pjmlp wrote:
               | No, actually referring to use of / in English grammar.
        
         | flohofwoe wrote:
         | >> ...POSIX and _t...
         | 
         | > IMO, that's not something to casually dismiss with a smiley.
         | If it's reserved, don't do it.
         | 
         | It only matters when your own type names collide with any of
         | the POSIX type names, and it's not like POSIX is changing much
         | nowadays. A collision with 'recent' C standard additions is
         | much more likely, and those are not predictable anyway (such as
         | alignas() or unreachable()).
        
       | heywhatupboys wrote:
       | > Wrap your structs in a typedef
       | 
       | NOOoooooo, please stop doing this. unless you are a library
       | author who gets to define things like uint8_t, please do not do
       | this
       | 
       | > Use struct wrappers for strong typing
       | 
       | even BIGGER no. This is completely misunderstanding what a
       | typedef is and what it should be used for.
        
         | properparity wrote:
         | >even BIGGER no. This is completely misunderstanding what a
         | typedef is and what it should be used for.
         | 
         | Hard disagree.
         | 
         | Life safer when dealing with things like SI unit types. I used
         | to use lots of suffixes - _km, _m, _seconds, _hours, but I
         | found that to be a lot more noisy (especially derived units and
         | Nth order stuff like acceleration, seconds_per_second, etc) and
         | evidently it would sneak in errors when you started doing
         | calculations and passing them to functions.
         | 
         | Definitely want different types when I have these three
         | representations flowing around in the program:
         | 
         | pressure_bits_t - raw data from the sensor, gets
         | filtered/averaged in this form, then converted to one of these
         | at various stages:
         | 
         | pressure_pascal_x10_t - integer pascal * 10 (i.e fixed point,
         | one decimal)
         | 
         | pressure_millibar_t - floating point in millibar
        
           | heywhatupboys wrote:
           | _t is even a "reserved" suffix in C. Stop using it. If you
           | want compile-time type safety, pick another language like
           | Haskell. A "typedef" is by no means a contract declaration in
           | C.
        
         | fathyb wrote:
         | Could you please share why wrapping structs in a typedef is
         | bad? I'm culpable of doing this quite often.
        
           | rramadass wrote:
           | There is nothing wrong with either practice. The GP is just
           | stating _their_ preferred style.
           | 
           | In fact it is good practice to use struct wrappers for void*
           | pointers to get type safety. On the other hand, a typedef is
           | just for programmer convenience and the compiler doesn't
           | care.
           | 
           | Eg: See DECLARE_HANDLE defined under STRICT at https://reneny
           | ffenegger.ch/notes/Windows/development/WinAPI/...
        
           | spacechild1 wrote:
           | It is common practice and IMO not bad at all.
        
           | enriquto wrote:
           | It is largely considered bad and misleading style. Why would
           | you ever hide that a variable is a srruct? Better to keep
           | typedef only for basic types and for function pointers.
        
             | properparity wrote:
             | Why would I ever care if something is a struct or not a
             | struct? Just that is almost zero useful information.
             | 
             | I do care about the size of a struct sometimes, but that
             | would require me to go to the definition of the struct, so
             | just seeing the word "struct" didn't help me one bit.
             | 
             | And I of course care about the members of the struct, but
             | that again requires me to know what the actual members are
             | which the word "struct" doesn't give me.
             | 
             | So what exactly does omitting the word "struct" hide again?
        
             | deschutes wrote:
             | The struct tag is an idea that's survived into
             | approximately zero other languages. It's largely visual
             | clutter.
        
             | moloch-hai wrote:
             | The only bad or misleading typedef is one that conceals
             | that a type is a pointer or reference, and therefore
             | implicitly converts to another type.
             | 
             | Technically, int32_t is bad in that way, but we are not
             | fooled by it.
        
               | unsafecast wrote:
               | > Technically, int32_t is bad in that way, but we are not
               | fooled by it.
               | 
               | Meaning? On my system int32_t is directly typedefed to
               | unsigned int, absolutely no hidden pointers or
               | conversions.
        
               | moloch-hai wrote:
               | int32_t promotes to long, converts to unsigned, and
               | truncates to short and char, all silently.
        
             | detrites wrote:
             | How is it hidden? Can't the inquiring mind simply examine
             | the codebase to see it's a struct? And if they choose not
             | to, isn't it on them if it turns out to be something other
             | than what they assumed it was without looking?
        
               | moloch-hai wrote:
               | It is hidden because you have to "examine" somewhere else
               | before you know. But it doesn't matter, because you don't
               | _need_ to know unless it is subject to invisible implicit
               | conversions. What might you do differently, having
               | "examined" the typedef?
        
               | Warwolt wrote:
               | I understand the preference for keeping the struct tag,
               | but virtually no other language does this, with modern
               | IDEs it's trivial to find out what the definition of a
               | given type is, so the tag seems superfluous to me in
               | practice.
        
               | Jtsummers wrote:
               | We don't even need modern IDEs. ctags solved this problem
               | last century.
        
               | rm445 wrote:
               | I think so. Even with a compiled library distributed with
               | a header file but no source, the header file needs to
               | forward declare something like 'typedef struct foo FOO;'
               | So (unless there's some clever trick I've missed) you can
               | always tell it's a struct but not necessarily see the
               | definition.
               | 
               | There is a point, perhaps a little specious, that you put
               | a module's data structures behind a typedef so the
               | interface doesn't change if it changes from a simple data
               | type to a struct. Probably doesn't happen too often.
               | 
               | The perfect C object-oriented-style interface is FILE*
               | from stdio.h. A FILE is a structure full of operating-
               | system-specific file information but you never have to
               | see it or worry about what's in it, you just use the
               | functions.
        
               | heywhatupboys wrote:
               | > There is a point, perhaps a little specious, that you
               | put a module's data structures behind a typedef so the
               | interface doesn't change if it changes from a simple data
               | type to a struct. Probably doesn't happen too often.
               | 
               | you could never do this in C. If it is a "value type"
               | i.e. a non-pointer, then you cannot change the size of
               | the value, without changing the ABI and the function
               | decl.
        
         | emsy wrote:
         | I think you don't understand why typedef is used for structs in
         | C and what do struct wrappers have to do with typedefs? It
         | seems to me you don't understand what is being said here.
        
       | pjmlp wrote:
       | Yeah, if there isn't any opinion available other than using C,
       | pity that security isn't part of modern C lexicon.
        
       | stabbles wrote:
       | You can have RAII with GCC extensions in C:
       | 
       | char *buf __attribute__ ((__cleanup__(free_buf))) = malloc(1024);
       | 
       | This calls free_buf(buf) when buf goes out of scope.
       | 
       | I've actually seen projects use it in the wild to close files or
       | free memory in branchy functions. [1]
       | 
       | [1]
       | https://github.com/containers/bubblewrap/blob/c54bbc6d7b78e7...
        
         | ndesaulniers wrote:
         | My biggest annoyance with the design of this extension is that
         | it must be applied explicitly to every instance of the given
         | type.
         | 
         | In C++, you get RAII destructor tied to the type, not the
         | instance.
         | 
         | With this GNU C extension, you must remember to use it, which
         | IMO is error prone.
        
         | kllrnohj wrote:
         | That's by far the least interesting usage of RAII and also,
         | annoyingly, the only one the article discusses.
         | 
         | The better usage _by far_ is describing ownership. Does a
         | function that takes a pointer take ownership of the pointer?
         | Who knows! It 's a mystery! Does a function that takes a
         | std::unique_ptr take ownership of the pointer? You're goddamn
         | right it does.
         | 
         | Now do the same with things like file descriptors. Managing FDs
         | in Linux is nightmare difficulty because if you get it wrong
         | there's almost never a crash or segfault to tell you about it.
         | Valgrind won't help you find it. Nothing helps you, you're
         | entirely on your own. In Rust the compiler validates ownership
         | for you, trivially made robust. In C++ you can make a
         | "unique_fd" or similar, and at least make accidental mistakes
         | harder. In C? Idk, apparently according to this article that's
         | just a "many small allocations" and you're just a shitty
         | programmer for doing that (wow, such useful advice lol)
        
         | moloch-hai wrote:
         | That is only a small fraction of RAII. The trivial fraction. If
         | malloc fails, real RAII gives you an exception, which you may
         | catch somewhere convenient. Real RAII runs a constructor. If
         | construction fails, you get an exception. Lacking those, it all
         | has to be done and checked by hand on the spot, and is often
         | wrong, because not tested.
        
           | JonChesterfield wrote:
           | Not sure that's the part of C++ I'd heap praise on. For one
           | thing, real RAII invokes the destructor, not a constructor.
           | If your constructor throws, the destructor isn't going to be
           | called. If your destructor throws, you're one passing
           | exception away from std::terminate. Plus malloc doesn't
           | throw, though new sometimes does.
           | 
           | Or, construction could return an optional/maybe style thing
           | that you branch on, and then you don't need goto (sorry, non-
           | local come-from with pretty branding) in your base language.
        
             | mgaunard wrote:
             | The important part of RAII is that the constructor actually
             | acquires the resource.
             | 
             | The fact the OP insisted the important part of RAII is not
             | that but running destructors shows he hasn't understood the
             | most important concept in C++ programming.
        
               | svvayy wrote:
               | The destructor is the distinguishing feature of RAII.
               | Lots of languages have constructors without RAII because
               | they do not have deterministic destructors. Examples are
               | Python and Java.
               | 
               | Can you expound on what your understanding is?
        
               | moloch-hai wrote:
               | The destructor is what distinguishes C++ from everything
               | before and all but Rust even after, but is, as already
               | explained above, just one part.
               | 
               | No exceptions, no RAII.
        
           | stabbles wrote:
           | The downside of C++ is that exceptions can come from any
           | possible function call depth, whereas in C you only have to
           | deal with error return codes. So in that sense it's easier to
           | deal with errors in C, and harder to test the error paths in
           | C++
        
             | pjmlp wrote:
             | Don't forget to check for EINTR and keep a list of
             | resources across the call stack in case a longjmp() is
             | called.
        
             | mhh__ wrote:
             | Ideally I think there is a time and a place for both.
             | 
             | If you can't test it you've probably got that balance
             | wrong.
             | 
             | That being said this realization is basically a post-
             | functional thing, so a lot of libraries are stuck with one
             | or the other.
        
             | moloch-hai wrote:
             | That is the _upside_. You don 't need to know how far down
             | it happened. If you need to know more, you catch it lower.
             | But most often you don't. Whatever the hell it was didn't
             | work, and you do whatever is called for, then.
        
               | colejohnson66 wrote:
               | Exactly. Being forced to handle errors at every function
               | call sounds great, but then you end being forced to
               | bubble up errors you don't care about like Go does:
               | res, err := func()         if err != nil:
               | return nil, err
               | 
               | That's not actually handling the error, and exceptions
               | save you from being forced to pretend like you are. Just
               | let it bubble up into the catch block further up the
               | stack.
        
               | properparity wrote:
               | You don't have to code like that in Go, C or other
               | languages without exceptions or fancy option-type sugar.
               | 
               | A sensible thing that often works great is to have a
               | sticky error state (either a single int, or list of stuff
               | you append to) then you just keep calling functions which
               | will append to and/or replace the current error state
               | until you reach a point where you can/care about handling
               | errors, then you examine the persistent state and do
               | something about it.
        
               | colejohnson66 wrote:
               | "Sticky states" work too; IIRC, it's how FPU exceptions
               | on the x87 work. But I was responding to the upthread
               | comment that "exceptions can come from any possible
               | function call depth, whereas in C you only have to deal
               | with error return codes." Sticky states still have that
               | "issue." After all, they're just an alternative to
               | try{}catch{} blocks.
        
               | moloch-hai wrote:
               | Or Google dialect.
               | 
               | Rust, anyway, captures this foolishness in a macro, which
               | saves lines of source code, but still costs cache
               | footprint, branch predictor slots, and runtime.
        
               | tialaramex wrote:
               | The Rust try! macro, which I assume is what you're
               | thinking of, has been obsolete for many years - you can
               | still refer to it because the Rust compatibility promise
               | is taken more seriously than in C++ but you will need to
               | use rather awkward syntax to get at it in modern Rust
               | editions since the keyword "try" is reserved since 2018
               | edition.
               | 
               | These days you'd use the Try operator ? which is not a
               | macro, it's an operator.
               | 
               | Try is really interesting, it's a unary operator so it
               | takes a single parameter and it maps that parameter into
               | a control flow decision. For Result the effect is similar
               | to what you got out of the try! macro, but of course this
               | operator can be implemented on any type. The standard
               | library provides six implementations, including famously
               | on Option, but also on ControlFlow itself, which is
               | pretty nice.
               | 
               | This means across a complex system you can choose to
               | collect Results, and decide what to do about the Results
               | later, (perhaps after you have all of them, or after
               | you've a certain amount) or you can choose the same for
               | the ControlFlow decisions resulting from those Results.
               | 
               | You can also turn things on their heads, and decide that
               | what you want to do is return early on success, but
               | continue processing for errors -- which is something
               | that's just unthinkable in an exception world where
               | control flow and success are somehow the same thing. Rust
               | took some years to figure out that's just not true which
               | would be embarrassing if the assumption that it's true
               | wasn't baked into the entire C++ language.
        
               | moloch-hai wrote:
               | You still wouldn't want to throw for the success case, so
               | the point is meaningless. And of course you can return
               | early on success in C++, and continue to process details
               | further otherwise, with zero difficulty, so there is no
               | cause for embarrassment.
               | 
               | Rust people should be embarrassed if they carved out
               | something special for this niche case.
        
               | tialaramex wrote:
               | > And of course you can return early on success in C++,
               | and continue to process details further otherwise, with
               | zero difficulty
               | 
               | Alas, C++ exceptions don't permit this, on failure the
               | exception will get thrown and control flow switches away
               | without any opportunity to intervene. That is in fact its
               | whole purpose. That's the design mistake, it's not
               | something you can fix, it's a choice which seemed clever
               | last century, and it's last century's design.
        
               | moloch-hai wrote:
               | It is in fact _absolutely trivial_ to do, if you care to.
               | But of course it is vanishingly rare to want it, so you
               | don 't see it much.
               | 
               | If you cannot figure it out, find someone to explain it
               | to you, instead doubling down when your falsehood is
               | pointed out.
        
         | RcouF1uZ4gsC wrote:
         | Apologies to Greenspun:
         | 
         | "Any sufficiently complicated C contains an ad hoc, informally-
         | specified, bug-ridden, slow implementation of half of C++."
         | 
         | See this hack as well as macros emulating templates for generic
         | data structures and roll your own vtables with function
         | pointers and _Generic for function overloading and function
         | prefixes for namespaces.
        
         | AlbertoGP wrote:
         | Yes, that is one way. I list this and others in the
         | documentation for my own implementation: https://sentido-
         | labs.com/en/library/cedro/202106171400/#defe...
        
         | bluecalm wrote:
         | Freeing stuff allocated in the same scope is both easy to do
         | and easy to catch when debugging. If you have multiple returns
         | just use goto FINALLY instead of returns and put the
         | deallocations before the return there.
        
         | WalterBright wrote:
         | C:                   char *buf __attribute__
         | ((__cleanup__(free_buf))) = malloc(1024);
         | 
         | D:                   char* buf = cast(char*)malloc(1024);
         | scope (exit) free(buf);
         | 
         | https://dlang.org/spec/statement.html#scope-guard-statement
         | 
         | The rationale for it:
         | 
         | https://dlang.org/articles/exception-safe.html
         | 
         | The idea for this came from Andrei Alexandrescu and Petru
         | Marginean who proposed it for C++:
         | 
         | http://drdobbs.com/184403758
        
       | WalterBright wrote:
       | > integer types have much clearer names now: uint8_t, uint16_t,
       | int32_t etc... (these are not built-in but defined in the
       | stdint.h header)
       | 
       | The problems:
       | 
       | 1. the C Standard Library doesn't use them
       | 
       | 2. integer promotions rules still pertain to unsigned char,
       | unsigned short, etc.
       | 
       | 3. more generally, the language semantics are not defined in
       | terms of those types
        
       | stephan-cr wrote:
       | > Especially /W4 on MSVC will generate a lot of fairly pointless
       | spam though, [...]
       | 
       | I think the author meant /Wall.
        
         | flohofwoe wrote:
         | I could have sworn /Wall is a fairly recent addition to MSVC,
         | but according to godbolt it seems to be supported since at
         | least VS2015.
        
       | dang wrote:
       | Discussed at the time:
       | 
       |  _Modern C for C++ Peeps (2019)_ -
       | https://news.ycombinator.com/item?id=27288145 - May 2021 (21
       | comments)
       | 
       |  _Modern C for C++ Peeps_ -
       | https://news.ycombinator.com/item?id=21093727 - Sept 2019 (16
       | comments)
        
       ___________________________________________________________________
       (page generated 2023-01-07 23:01 UTC)