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