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