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.
> 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.
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.
Very little of the standard C++ library is not "kernel safe". If a bit of it might need to allocate, you may provide it a kernel-grade allocator to use; but you probably use custom kernel containers, for other reasons. If you don't want any filebufs, you do not make them.
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.
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.
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.
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?
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.
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.
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.
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.