Yes, yes you can technically use a fairly restricted subset of C++ or whatever and then you can avoid some C-isms at the cost of risking runtime panics on the new operator.
If you’re referring to compiling with exceptions disabled, you avoid such crashes by using the nothrow variant of operator new, so an allocation failure just returns a null pointer.
There’s really no cost to using “C++ without exceptions and RTTI” relative to using C. Right now I’m working on a project for an embedded ARM32 device with 20KB of RAM and about 200KB ROM, and I’m doing it in C++. I’m using classes, references, and a few templates, but zero heap allocation, not even using the C standard library. Currently it’s about 30K of code and a couple hundred bytes of global data.
C++ is simpler than Rust, if you don’t try to make your own templates. There’s kind of a bunch of features that all add on to C — references, overloading, classes — that you can mix & match. And using the standard templates like vector, optional, map, etc. isn’t too bad, although the design isn’t the greatest. But actually creating templates is kind of a rabbit hole that I’ve never gone very far down.
In general, though, I don’t see why anyone would write C anymore. It’s like Middle English.
C++ standards move much faster and tend to break compatibility more, the ABI guarantees are much less useful, compilation times are much worse, etc. Some people just prefer a simpler, more stable language.
For *NIX platforms, the C++ ABI is the Itanium ABI and this has remained backwards compatible for about 20 years. GCC 3.0 was released in 2001 and every version of GCC and clang since then has supported the same ABI for C++.
The ABI of any given library is typically weaker, but C++ isn’t worse than C in this regard. If you modify a struct layout between C library versions, you break the ABI. Similarly if you add or remove fields in a C++ class or struct, you change the ABI. If you expose a struct of function pointers in C and change the layout, you break the ABI. If you add or remove vtable methods in a C++ class, exactly the same applies.
All I can say is I haven’t had problems going from ’11 to ‘14 to ‘17, but then I always turn on lots of warnings and -Werror and run UBsan, so maybe I don’t leave questionable stuff in my code that hits such edge cases.
The ABI is unquestionably really poor for things like dynamic libraries, but it’s no worse than C’s since all you have to do is add some extern “C” and you’ve got the C ABI at library boundaries.
Yes, but unless your codebase is huge they’re not terrible. Even when I worked on Chrome ten+ years ago, on a 2008 Mac Pro with a spinning disk, turnaround times were about 20sec in most cases. In my current project it’s more like 5sec.
IMO these things are worth it for a more expressive, safer language where I’m not constantly juggling raw pointers and manual memory management.
All I can say is I haven’t had problems going from ’11 to ‘14 to ‘17, but then I always turn on lots of warnings and -Werror and run UBsan, so maybe I don’t leave questionable stuff in my code that hits such edge cases.
I’m speaking from the perspective of a distributor / package maintainer, where I have much more trouble keeping C++ code from a few months ago working (whenever anything is changed, be it boost or the compiler) compared to C code from 20 years ago.
C++ is simpler than Rust, if you don’t try to make your own templates
That’s a little bit sad, because for systems-programming tasks templates are exactly where C++’s power comes from. For example, snmalloc’s platform abstraction layer is built using templates and inlines to either nothing or calls to individual platform-specific code.
Actually, snmalloc uses templates all through the codebase, making it really easy to plug in different bits of behaviour.
It’s much harder in C to create type-safe compile-time specialisation. The C++ template syntax is pretty painful, but once you get comfortable with it then you have a vastly more powerful language. We generally turn off exceptions and RTTI and strongly discourage inheritance in C++, but we get huge benefits from templates.
Most recently, I wrote some code that inspects the mcontext in a signal handler for signals that are raised from sandbox violations, pulls out the arguments from the frame, calls a function that does an IPC to emulate the system call in the parent process, and then injects the result back into the signal context. I have absolutely no idea how I’d write this in maintainable C, but in C++ there are Linux and FreeBSD that abstract over the details of the signal frame, a templated function that invokes this with types extracted from the argument types of the function that handles the fallback and then a generic lambda that invokes the template function with inferred type arguments. If you look at the generated code for the release builds, this folds down to a handful of instructions. In C, I can’t imagine this being anything other than either a bunch of scary type casts and incomprehensible macros.
I agree templates are really powerful, but in my experience they’re the sharp knee of the language learning curve. My post was aiming to let C users know they can approach C++ as a set of very useful and tasty condiments they can pick and choose from to add to the vanilla soft-serve that is C. Defining templates doesn’t fit there.
I’ve seen people take that approach and they tend to write the kind of awful C++ code that makes other people avoid C++. Templates have a steep learning curve, but they (especially in composition with constexpr) also have a huge return on that investment. The big benefit of C++ over C for low-level tasks is the ability to do aggressive compile-time specialisation.
The bigger problem in Linux is that most of the headers are invalid C++. I recently tried writing C++ code in the Linux kernel. In the FreeBSD kernel it’s mostly fine (I needed to undefine a few macros) but with Linux you get huge numbers of compiler errors just from a single #include when the language is set to C++.
Ow! That’s awful. It’s very rare that I run across a C header I can’t use in C++ simply by wrapping extern "C" { ... } around it. I’ve done a bit of Linux coding, but only using the standard C library headers, never kernel stuff.
If you’re referring to compiling with exceptions disabled, you avoid such crashes by using the nothrow variant of operator new, so an allocation failure just returns a null pointer.
There’s really no cost to using “C++ without exceptions and RTTI” relative to using C. Right now I’m working on a project for an embedded ARM32 device with 20KB of RAM and about 200KB ROM, and I’m doing it in C++. I’m using classes, references, and a few templates, but zero heap allocation, not even using the C standard library. Currently it’s about 30K of code and a couple hundred bytes of global data.
Today I learned that was a thing! I’m not up to snuff on C++ (it’s always scared me) but I’ll see about editing that quip in a bit.
C++ is simpler than Rust, if you don’t try to make your own templates. There’s kind of a bunch of features that all add on to C — references, overloading, classes — that you can mix & match. And using the standard templates like vector, optional, map, etc. isn’t too bad, although the design isn’t the greatest. But actually creating templates is kind of a rabbit hole that I’ve never gone very far down.
In general, though, I don’t see why anyone would write C anymore. It’s like Middle English.
Muffled sobbing from the corner where J.R.R. Tolkien is quaffing ale
Aye, I was unhappy with that simile but had to finish the comment in a hurry because real life.
How about “it’s like a steam engine?”
C++ standards move much faster and tend to break compatibility more, the ABI guarantees are much less useful, compilation times are much worse, etc. Some people just prefer a simpler, more stable language.
For *NIX platforms, the C++ ABI is the Itanium ABI and this has remained backwards compatible for about 20 years. GCC 3.0 was released in 2001 and every version of GCC and clang since then has supported the same ABI for C++.
The ABI of any given library is typically weaker, but C++ isn’t worse than C in this regard. If you modify a
struct
layout between C library versions, you break the ABI. Similarly if you add or remove fields in a C++class
orstruct
, you change the ABI. If you expose a struct of function pointers in C and change the layout, you break the ABI. If you add or remove vtable methods in a C++ class, exactly the same applies.extern “C”
and you’ve got the C ABI at library boundaries.IMO these things are worth it for a more expressive, safer language where I’m not constantly juggling raw pointers and manual memory management.
I’m speaking from the perspective of a distributor / package maintainer, where I have much more trouble keeping C++ code from a few months ago working (whenever anything is changed, be it boost or the compiler) compared to C code from 20 years ago.
That’s a little bit sad, because for systems-programming tasks templates are exactly where C++’s power comes from. For example, snmalloc’s platform abstraction layer is built using templates and inlines to either nothing or calls to individual platform-specific code. Actually, snmalloc uses templates all through the codebase, making it really easy to plug in different bits of behaviour.
It’s much harder in C to create type-safe compile-time specialisation. The C++ template syntax is pretty painful, but once you get comfortable with it then you have a vastly more powerful language. We generally turn off exceptions and RTTI and strongly discourage inheritance in C++, but we get huge benefits from templates.
Most recently, I wrote some code that inspects the
mcontext
in a signal handler for signals that are raised from sandbox violations, pulls out the arguments from the frame, calls a function that does an IPC to emulate the system call in the parent process, and then injects the result back into the signal context. I have absolutely no idea how I’d write this in maintainable C, but in C++ there are Linux and FreeBSD that abstract over the details of the signal frame, a templated function that invokes this with types extracted from the argument types of the function that handles the fallback and then a generic lambda that invokes the template function with inferred type arguments. If you look at the generated code for the release builds, this folds down to a handful of instructions. In C, I can’t imagine this being anything other than either a bunch of scary type casts and incomprehensible macros.I agree templates are really powerful, but in my experience they’re the sharp knee of the language learning curve. My post was aiming to let C users know they can approach C++ as a set of very useful and tasty condiments they can pick and choose from to add to the vanilla soft-serve that is C. Defining templates doesn’t fit there.
I’ve seen people take that approach and they tend to write the kind of awful C++ code that makes other people avoid C++. Templates have a steep learning curve, but they (especially in composition with
constexpr
) also have a huge return on that investment. The big benefit of C++ over C for low-level tasks is the ability to do aggressive compile-time specialisation.The bigger problem in Linux is that most of the headers are invalid C++. I recently tried writing C++ code in the Linux kernel. In the FreeBSD kernel it’s mostly fine (I needed to undefine a few macros) but with Linux you get huge numbers of compiler errors just from a single
#include
when the language is set to C++.Ow! That’s awful. It’s very rare that I run across a C header I can’t use in C++ simply by wrapping
extern "C" { ... }
around it. I’ve done a bit of Linux coding, but only using the standard C library headers, never kernel stuff.Great to see the Rust for Linux work being exercised outside of their own examples.