50

I am just curious why drivers and firmwares almost always are written in C or Assembly, and not C++?

I have heard that there is a technical reason for this.

Does anyone know this?

Lots of love, Louise

Louise
  • 6,193
  • 13
  • 36
  • 36
  • 10
    C++ has cruft. Drivers and embedded systems need to be really lightweight. – Anon. Jan 11 '10 at 01:37
  • 3
    +1 for cruft. I'd add that the cruft affects speed of execution and drivers/firmwares are mostly performance sensitive and you want them to be as fast as possible. – Sudhanshu Jan 11 '10 at 01:40
  • Because most microprocessor IDEs (See Turbo C, Dynamic C) etc are enough high level language to build a decent program and run. Very few are written in ASM anymore. –  Jan 11 '10 at 01:42
  • 1
    I would swear we have done this before, but can't find an example. However, my search turned up some related questions that may be of interest: http://stackoverflow.com/questions/994600/writing-drivers-in-c http://stackoverflow.com/questions/683701/is-it-possible-to-code-a-device-driver-in-java http://stackoverflow.com/questions/981200/can-windows-drivers-be-written-in-python – dmckee --- ex-moderator kitten Jan 11 '10 at 02:57
  • 12
    With C, you end up hand-coding the same "cruft" C++ had in the first place: polymorphism -> descriptors, classes-> modules, templates -> function-like macros, namespaces -> name prefixes etc. See my answer. – Emile Cormier Jan 11 '10 at 03:05
  • I see some good responses here, but one thing that people left out is the justification for assembly. You simply _CAN'T DO_ everything the CPU has to offer with C or C++. You need assembly for stuff like telling the CPU about your page tables, or swapping stacks and saving registers for a context switch, or setting up the first few instructions of an IRQ handler, or doing basic atomic operations. – asveikau Jan 11 '10 at 08:06
  • I agree with asveiku -- the most important difference which causes C and asm to be used for firmware is that C++ has language features which require various infrastructure. In particular, dynamic memory management has to be implemented in order for most C++ code to run. You could write firmware in a subset of C++, but the curtailed version of C++ would be not very different from C. In the very beginning of the firmware, the power-on circuitry calls an address in the code known as the 'reset vector.' That function must be written in asm -- it can then call main() if using C. – Heath Hunnicutt Feb 14 '10 at 23:49
  • 1
    @Heath - my experience hasn't been that "dynamic memory management has to be implemented in order for most C++ code to run", far from it. And even if so, you can implement your own memory management (replace new/delete on globally or on a per-class basis). My experience is that if your C implementation wouldn't need dynamic memory, neither would the C++ implementation. An exception might be if your C implementation uses "roll your own" linked lists, etc. & the C++ version uses the standard lib containers (list, vector, etc.) – Dan Jan 26 '11 at 13:40

15 Answers15

31

Because, most of the time, the operating system (or a "run-time library") provides the stdlib functionality required by C++.

In C and ASM you can create bare executables, which contain no external dependencies.

However, since windows does support the C++ stdlib, most Windows drivers are written in (a limited subset of) C++.

Also when firmware is written ASM it is usually because either (A) the platform it is executing on does not have a C++ compiler or (B) there are extreme speed or size constraints.

Note that (B) hasn't generally been an issue since the early 2000's.

John Gietzen
  • 48,783
  • 32
  • 145
  • 190
  • Windows drivers don't use MFC. – Billy ONeal Jan 11 '10 at 01:42
  • 4
    Does that mean that all C++ programs have library dependencies? – Louise Jan 11 '10 at 01:43
  • 4
    @Louise: No, not necessarily. I think you could statically link the stdlib in, but I'm not sure that you can make a bare executable that way. Anyways, on Windows, drivers are C++ and all of them do have external dependencies. – John Gietzen Jan 11 '10 at 01:45
  • 2
    +1 for correct answer. It's not because of the extra few bytes of "cruft" in class instances, as some others say. Also, it should be mentioned that C compilers are much easier to write than C++ compilers, and many embedded systems run on cheaper or lesser-used processors which simply don't have a (decent) C++ compiler. – BlueRaja - Danny Pflughoeft Jan 11 '10 at 02:15
  • "the processor it is excuting on does not have a C or C++ compiler": That seems a bit fishy to me. C compilers are quite easy to implement and widely ported (that was one of C's explicit design goals); I've never heard of a modern processor w/o a C compiler. Can you give any references? – sleske Jan 11 '10 at 02:15
  • 1
    I'm not saying that is the case, just that that would be a feasible excuse to use ASM. Like, if there was a new RISC processor, the first compiler would be written in bytecode, then in ASM, and then in whatever... – John Gietzen Jan 11 '10 at 02:17
  • 2
    @sleske: See http://www.parallax.com/propeller/ for a modern chip without a C compiler. It's a multicore architecture with a unique hardware 'scheduler' to manage access to resources. – slebetman Jan 11 '10 at 02:28
  • Even if a processor has a C compiler, if the processor is obscure or relatively new, the compiler may not do a very good job of optimizing code for it. Especially in real-time applications (but in other cases as well), this may force one to use ASM. – Stephen Canon Jan 11 '10 at 02:42
  • 2
    I'm not sure C is as common in bootstrapping as the C advocates tend to claim. For example, the first resident software on both the Intel 8086 and the Apple Macintosh was Forth, another language known for its simplicity and ease of porting. – Ken Jan 11 '10 at 02:55
  • @Ken:The Mac came out in 1984, when C compilers will still relatively rare (and C programmers at least equally so). The 8086 came out years earlier still, at a time that C was restricted almost entirely to Unix, and Unix almost entirely to universities and a *few* companies that happened to have close ties to Bell Labs. – Jerry Coffin Jan 11 '10 at 04:27
  • @Ken: "The first resident software"? The first compiler, maybe, but that's because a Forth compiler is typically simpler than an assembler. Initial development for those machines was done on bigger machines with assemblers and nice filesystems. – Potatoswatter Jan 11 '10 at 06:34
  • 1
    Wrong - the lack of c++ runtimes is a symptom. They are not there because they're not needed. There not needed because even if they were there, C++ drivers would still be a no-go. The problems with C++ in device drivers are - c++ code, of necessity, uses the C++ runtime - which uses exceptions. C++ compilers also implicit code - default constructors and conversion operators. both of which violate constraints that device drivers need to meet. – Chris Becke Jan 11 '10 at 08:10
  • Keep in mind that when you write a kernel in C, you're not using the "standard" C library either. You're using whatever routines have been written for that particular kernel. I think the reason you don't see more C++ libraries ported to kernels is because libraries don't exist "by default" - someone has to write them. Aside from the matter of what to do about exceptions or memory allocation, your average C++ standard library probably contains lots of implicit dependency on syscalls and the like and it would take effort to port that; effort for little gain. – asveikau Jan 11 '10 at 17:03
  • 1
    Chris, exceptions are not required. See G++ -fnoexception for an example. As for implicit class methods, declare them but do not define them and most compilers will omit their creation. – Don Wakefield Dec 18 '10 at 17:24
  • I feel there is a lot of confusion between the language C++ and what its library can provide. C++ is modular, and only slightly interdependent. What I mean is that not all features need to be provided. See for example the Android NDK, which does not support exceptions (although 3rd party solutions re readily available). If you don't need it, don't include it. I'm on the side of: "most of the cruft C++ has built-in will be rewritten if implemented in C", but hey, who am I... – rubenvb Jan 25 '11 at 18:52
27

Code in the kernel runs in a very different environment than in user space. There is no process separation, so errors are a lot harder to recover from; exceptions are pretty much out of the question. There are different memory allocators, so it can be harder to get new and delete to work properly in a kernel context. There is less of the standard library available, making it a lot harder to use a language like C++ effectively.

Windows allows the use of a very limited subset of C++ in kernel drivers; essentially, those things which could be trivially translated to C, such as variable declarations in places besides the beginning of blocks. They recommend against use of new and delete, and do not have support for RTTI or most of the C++ standard library.

Mac OS X use I/O Kit, which is a framework based on a limited subset of C++, though as far as I can tell more complete than that allowed on Windows. It is essentially C++ without exceptions and RTTI.

Most Unix-like operating systems (Linux, the BSDs) are written in C, and I think that no one has ever really seen the benefit of adding C++ support to the kernel, given that C++ in the kernel is generally so limited.

Brian Campbell
  • 322,767
  • 57
  • 360
  • 340
  • 3
    "Variable declarations in places besides the beginning of blocks" is actually part of C now. So it's *really* easy to translate to C. :-) – Ken Jan 11 '10 at 02:51
  • 1
    Well, yes, you can do that in C99, but Microsoft's compiler doesn't support C99, so to get that feature you need to use C++ (I meant to write a caveat about that in my answer, but I guess I forgot to). – Brian Campbell Jan 11 '10 at 03:01
  • Additionally, C++ libraries and templates make it very easy to accidentally produce complicated, bloated machine code, and its binary calling conventions have historically been less clearly defined than C (see: ABI). Many kernel maintainers don't want the headache of policing contributions to make sure they avoid such pitfalls. – ʇsәɹoɈ Jun 12 '10 at 20:39
11

1) "Because it's always been that way" - this actually explains more than you think - given that the APIs on pretty much all current systems were originally written to a C or ASM based model, and given that a lot of prior code exists in C and ASM, it's often easier to 'go with the flow' than to figure out how to take advantage of C++.

2) Environment - To use all of C++'s features, you need quite a runtime environment, some of which is just a pain to provide to a driver. It's easier to do if you limit your feature set, but among other things, memory management can get very interesting in C++, if you don't have much of a heap. Exceptions are also very interesting to consider in this environment, as is RTTI.

3) "I can't see what it does". It is possible for any reasonably skilled programmer to look at a line of C and have a good idea of what happens at a machine code level to implement that line. Obviously optimization changes that somewhat, but for the most part, you can tell what's going on. In C++, given operator overloading, constructors, destructors, exception, etc, it gets really hard to have any idea of what's going to happen on a given line of code. When writing device drivers, this can be deadly, because you often MUST know whether you are going to interact with the memory manager, or if the line of code affects (or depends on) interrupt levels or masking.

It is entirely possible to write device drivers under Windows using C++ - I've done it myself. The caveat is that you have to be careful about which C++ features you use, and where you use them from.

Michael Kohne
  • 11,888
  • 3
  • 47
  • 79
11

Except for wider tool support and hardware portability, I don't think there's a compelling reason to limit yourself to C anymore. I often see complicated hand-coded stuff done in C that can be more naturally done in C++:

  • The grouping into "modules" of functions (non-general purpose) that work only on the same data structure (often called "object") -> Use C++ classes.
  • Use of a "handle" pointer so that module functions can work with "instances" of data structures -> Use C++ classes.
  • File scope static functions that are not part of a module's API -> C++ private member functions, anonymous namespaces, or "detail" namespaces.
  • Use of function-like macros -> C++ templates and inline/constexpr functions
  • Different runtime behavior depending on a type ID with either hand-made vtable ("descriptor") or dispatched with a switch statement -> C++ polymorphism
  • Error-prone pointer arithmetic for marshalling/demarshalling data from/to a communications port, or use of non-portable structures -> C++ stream concept (not necessarily std::iostream)
  • Prefixing the hell out of everything to avoid name clashes: C++ namespaces
  • Macros as compile-time constants -> C++11 constexpr constants
  • Forgetting to close resources before handles go out of scope -> C++ RAII

None of the C++ features described above cost more than the hand-written C implementations. I'm probably missing some more. I think the inertia of C in this area has more to do with C being mostly used.

Of course, you may not be able to use STL liberally (or at all) in a constrained environment, but that doesn't mean you can't use C++ as a "better C".

Emile Cormier
  • 28,391
  • 15
  • 94
  • 122
  • 2
    +1: for differentiating between the pure language and the STL part of the language. – rubenvb Dec 18 '10 at 17:01
  • +1 for a very even-handed discussion. Also, 3rd bullet item, I might also suggest inline functions as another alternative to function-like macros (although I suppose technically "inline" is available in C99 too -- although most shops I work with don't use it) – Dan Jan 26 '11 at 13:47
  • @Dan: Added inline functions. Thanks. – Emile Cormier Jan 26 '11 at 18:06
  • 10 years later... I'm working a hobby Arduino project right now with a puny MCU having 2k RAM and 32k usable flash. I started with a C-based sketch, and converted it to C++11 just for fun. As I refactored things to C++, the memory/flash usage would barely grow compared the C equivalent. I even wrote a full-blown `string_view` and `static_string` class that uses the C runtime library for low-level string operations. Whatever small increases I had with ram/rom usage was due to the design becoming more extensible and components becoming more reusable (with less stuff being hard-coded). – Emile Cormier Oct 04 '20 at 23:18
10

The comments I run into as why a shop is using C for an embedded system versus C++ are:

  1. C++ produces code bloat
  2. C++ exceptions take up too much room.
  3. C++ polymorphism and virtual tables use too much memory or execution time.
  4. The people in the shop don't know the C++ language.

The only valid reason may be the last. I've seen C language programs that incorporate OOP, function objects and virtual functions. It gets very ugly very fast and bloats the code.

Exception handling in C, when implemented correctly, takes up a lot of room. I would say about the same as C++. The benefit to C++ exceptions: they are in the language and programmers don't have to redesign the wheel.

The reason I prefer C++ to C in embedded systems is that C++ is a stronger typed language. More issues can be found in compile time which reduces development time. Also, C++ is an easier language to implement Object Oriented concepts than C.

Most of the reasons against C++ are around design concepts rather than the actual language.

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
  • The benefit to C exception, C virtual function, and C OOP that makes it crucial especially for drivers and firmwares: they should not exist at all in drivers. Embedded != Drivers; Android is embedded systems, but it is primarily written in Java except the kernel and drivers is in C. OS kernel usually provides memory safety net, but the kernel itself does not benefit from the safety net, so writing for drivers and firmwares need strong guarantees that cannot be as easily enforced by C++ unless you're using a very limited subset of C++ that the limited subset you're using will no longer be C++. – Lie Ryan Jan 25 '11 at 18:47
  • Size has very little to do with not using C++. However, I would agree with you, that going with the flow is a major reason. Noone bothered to figure out what subset of C++ that can be safely used in drivers development, since they know they will just produce something which is "like C and C++ but neither C nor C++", IOW another new language which noone will bother to learn. – Lie Ryan Jan 25 '11 at 18:51
  • +1 for "The people in the shop don't know the C++ language." That is very typical, and also a valid reason to avoid the language. Like you, I tend to use C++ when I can, but often if I'm working with a customer that is a C-only shop, I usually stay with straight C. Kind of a chicken & egg situation, but it is what it is. Most fun is when I work w/ a place that wants to move to C++ & is willing to do it deliberately & carefully. – Dan Jan 26 '11 at 13:50
7

The biggest reason C is used instead of say extremely guarded Java is that it is very easy to keep sight of what memory is used for a given operation. C is very addressing oriented. Of key concern in writing kernel code is avoiding referencing memory that might cause a page fault at an inconvenient moment.

C++ can be used but only if the run-time is specially adapted to reference only internal tables in fixed memory (not pageable) when the run-time machinery is invoked implicitly eg using a vtable when calling virtual functions. This special adaptation does not come "out of the box" most of the time.

Integrating C with a platform is much easier to do as it is easy to strip C of its standard library and keep control of memory accesses utterly explicit. So what with it also being a well-known language it is often the choice of kernel tools designers.

Edit: Removed reference to new and delete calls (this was wrong/misleading); replaced with more general "run-time machinery" phrase.

martinr
  • 3,794
  • 2
  • 17
  • 15
7

The reason that C, not C++ is used is NOT:

  • Because C++ is slower
  • Or because the c-runtime is already present.

It IS because C++ uses exceptions. Most implementations of C++ language exceptions are unusable in driver code because drivers are invoked when the OS is responding to hardware interrupts. During a hardware interrupt, driver code is NOT allowed to use exceptions as that would/could cause recursive interrupts. Also, the stack space available to code while in the context of an interrupt is typically very small (and non growable as a consequence of the no exceptions rule).

You can of course use new(std::nothrow), but because exceptions in c++ are now ubiqutious, that means you cannot rely on any library code to use std::nothrow semantics.

It IS also because C++ gave up a few features of C :- In drivers, code placement is important. Device drivers need to be able to respond to interrupts. Interrupt code MUST be placed in code segments that are "non paged", or permanently mapped into memory, as, if the code was in paged memory, it might be paged out when called upon, which will cause an exception, which is banned. In C compilers that are used for driver development, there are #pragma directives that can control which type of memory functions end up on. As non paged pool is a very limited resource, you do NOT want to mark your entire driver as non paged: C++ however generates a lot of implicit code. Default constructors for example. There is no way to bracket C++ implicitly generated code to control its placement, and because conversion operators are automatically called there is no way for code audits to guarantee that there are no side effects calling out to paged code.

So, to summarise :- The reason C, not C++ is used for driver development, is because drivers written in C++ would either consume unreasonable amounts of non-paged memory, or crash the OS kernel.

Chris Becke
  • 34,244
  • 12
  • 79
  • 148
  • -1 There is no research or fact that C++ runs slower than C. When the equivalent functionality is incorporated into C, they both run the same speed. – Thomas Matthews Jan 11 '10 at 18:04
  • 6
    I realize that irony is difficult to detect when written. However, when I said that the speed difference was NOT why c++ is avoided for driver dev, I thought tt obvious that the reader would understand that I was not implying there is a speed difference. – Chris Becke Jan 11 '10 at 18:15
5

C is very close to a machine independent assembly language. Most OS-type programming is down at the "bare metal" level. With C, the code you read is the actual code. C++ can hide things that C cannot.

This is just my opinion, but I've spent a lot of time in my life debugging device drivers and OS related things. Often by looking at assembly language. Keep it simple at the low level and let the application level get fancy.

Richard Pennington
  • 19,673
  • 4
  • 43
  • 72
3

Windows drivers are written in C++.
Linux drivers are written in c because the kernel is written in c.

Martin Beckett
  • 94,801
  • 28
  • 188
  • 263
2

Probably because c is still often faster, smaller when compiled, and more consistent in compilation between different OS versions, and with fewer dependencies. Also, as c++ is really built on c, the question is do you need what it provides?

There is probably something to the fact that people that write drivers and firmware are usually used to working at the OS level (or lower) which is in c, and therefore are used to using c for this type of problem.

Andrew Kuklewicz
  • 10,621
  • 1
  • 34
  • 42
2

The reason that drivers and firmwares are mostly written in C or ASM is, there is no dependency on the actual runtime libraries. If you were to imagine this imaginary driver written in C here

#include <stdio.h>

#define OS_VER   5.10
#define DRIVER_VER "1.2.3"

int drivermain(driverstructinfo **dsi){
   if ((*dsi)->version > OS_VER){
       (*dsi)->InitDriver();
       printf("FooBar Driver Loaded\n");
       printf("Version: %s", DRIVER_VER);
       (*dsi)->Dispatch = fooDispatch;
   }else{
       (*dsi)->Exit(0);
   }
}

void fooDispatch(driverstructinfo *dsi){
   printf("Dispatched %d\n", dsi->GetDispatchId());
}

Notice that the runtime library support would have to be pulled in and linked in during compile/link, it would not work as the runtime environment (that is when the operating system is during a load/initialize phase) is not fully set up and hence there would be no clue on how to printf, and would probably sound the death knell of the operating system (a kernel panic for Linux, a Blue Screen for Windows) as there is no reference on how to execute the function.

Put it another way, with a driver, that driver code has privilege to execute code along with the kernel code which would be sharing the same space, ring0 is the ultimate code execution privilege (all instructions allowed), ring3 is where the front end of the operating system runs in (limited execution privilege), in other words, a ring3 code cannot have a instruction that is reserved for ring0, the kernel will kill the code by trapping it as if to say 'Hey, you have no privilege to tread up ring0's domain'.

The other reason why it is written in assembler, is mainly for code size and raw native speed, this could be the case of say, a serial port driver, where input/output is 'critical' to the function in relation to timing, latency, buffering.

Most device drivers (in the case of Windows), would have a special compiler toolchain (WinDDK) which can use C code but has no linkage to the normal standard C's runtime libraries.

There is one toolkit that can enable you to build a driver within Visual Studio, VisualDDK. By all means, building a driver is not for the faint of heart, you will get stress induced activity by staring at blue screens, kernel panics and wonder why, debugging drivers and so on.

The debugging side is harder, ring0 code are not easily accessible by ring3 code as the doors to it are shut, it is through the kernel trap door (for want of a better word) and if asked politely, the door still stays shut while the kernel delegates the task to a handler residing on ring0, execute it, whatever results are returned, are passed back out to ring3 code and the door still stays shut firmly. That is the analogy concept of how userland code can execute privileged code on ring0.

Furthermore, this privileged code, can easily trample over the kernel's memory space and corrupt something hence the kernel panic/bluescreens...

Hope this helps.

t0mm13b
  • 34,087
  • 8
  • 78
  • 110
1

Perhaps because a driver doesn't require object oriented features, while the fact that C still has somewhat more mature compilers would make a difference.

pavpanchekha
  • 2,073
  • 1
  • 17
  • 23
1

There are many style of programming such as procedural, functional, object oriented etc. Object oriented programming is more suited for modeling real world.

I would use object-oriented for device drivers if it suites it. But, most of the time when you programming device drivers, you would not need the advantages provided by c++ such as, abstraction, polymorphism, code reuse etc.

chinmaya
  • 609
  • 3
  • 5
1

Well, IOKit drivers for MacOSX are written in C++ subset (no exceptions, templates, multiple inheritance). And there is even a possibility to write linux kernel modules in haskell.)

Otherwise, C, being a portable assembly language, perfectly catches the von Neumann architecture and computation model, allowing for direct control over all it's peculiarities and drawbacks (such as the "von Neumann bottleneck"). C does exactly what it was designed for and catches it's target abstraction model completely and flawlessly (well except for implicit assumption in single control flow which could have been generalized to cover the reality of hardware threads) and this is why i think it is a beautiful language.) Restricting the expressive power of the language to such basics eliminates most of the unpredictable transformation details when different computational models are being applied to this de-facto standard. In other words, C makes you stick to basics and allows pretty much direct control over what you are doing, for example when modeling behavior commonality with virtual functions you control exactly how the function pointer tables get stored and used when comparing to C++'s implicit vtbl allocation and management. This is in fact helpful when considering caches.

Having said that, object-based paradigm is very useful for representing physical objects and their dependencies. Adding inheritance we get object-oriented paradigm which in turn is very useful to represent physical objects' structure and behavior hierarchy. Nothing stops anyone from using it and expressing it in C again allowing full control over exactly how your objects will be created, stored, destroyed and copied. In fact that is the approach taken in linux device model. They got "objects" to represent devices, object implementation hierarchy to model power management dependancies and hacked-up inheritance functionality to represent device families, all done in C.

Inso Reiges
  • 1,889
  • 3
  • 17
  • 30
0

because from system level, drivers need to control every bits of every bytes of the memory, other higher language cannot do that, or cannot do that natively, only C/Asm achieve~