We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

C++ for small devices

00:00

Formal Metadata

Title
C++ for small devices
Title of Series
Number of Parts
170
Author
License
CC Attribution - NonCommercial - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Identifiers
Publisher
Release Date
Language

Content Metadata

Subject Area
Genre
Abstract
In this talk we will have a look at the old truth that C++ is a bad fit for embedded product development. Some would have you believe that C++ is too resource hungry and has to be stripped down to be usable in this domain. While there are cases where this is true, we will have a look at the larger and growing number of cases where it isn’t. It is time that we try to dig out some facts and start making architectural decisions based on that instead of old truths.
3
Thumbnail
59:06
71
112
127
130
WhiteboardScale (map)Food energyFreewareComputer configurationArchitectureArmUsabilitySoftware developerRun time (program lifecycle phase)ImplementationCodeError messageException handlingCodierung <Programmierung>Data storage deviceResource allocationRead-only memoryAerodynamicsCondition numberVariable (mathematics)Element (mathematics)Control flowFunction (mathematics)Interface (computing)Data conversionObject (grammar)Integrated development environmentOperator (mathematics)Constructor (object-oriented programming)Rule of inferenceSocial classPersonal digital assistantScripting languageLinker (computing)OvalState diagramCompact spaceEmbedded systemNumberTemplate (C++)Type theoryStudent's t-testCompilation albumCompilerMereologyConstructor (object-oriented programming)Shared memoryExtension (kinesiology)Sound effectContext awarenessData managementDifferent (Kate Ryan album)Compass (drafting)Doubling the cubeException handlingMemory managementStructural loadECosResultantPower (physics)Forcing (mathematics)Functional (mathematics)Sheaf (mathematics)Data structureSoftware developerCASE <Informatik>40 (number)WritingPointer (computer programming)SurfaceSource codeElectronic mailing listPhysical systemLoop (music)Function (mathematics)Multiplication signLibrary (computing)Fitness functionImplementationMetropolitan area networkCodeSemiconductor memoryVirtual machineBitPeer-to-peerComputer architectureArithmetic meanInformationCondition numberDomain nameBoundary value problemIntegrated development environmentElement (mathematics)Social classInterface (computing)String (computer science)Table (information)WebsiteQuicksortCasting (performing arts)Reading (process)Exterior algebraOperator (mathematics)SpacetimeFormal languageRange (statistics)Single-precision floating-point formatPhysical lawRegulator geneDecision theoryRight angleAreaLine (geometry)RecursionCartesian coordinate systemMicrocontrollerProjective planeRotationVirtualizationPoint (geometry)Inheritance (object-oriented programming)Classical physicsComputer programming10 (number)Object (grammar)VotingSlide ruleEndliche ModelltheorieProcess (computing)Execution unitNumberRow (database)System callTrailCuboidParameter (computer programming)Resource allocationStatement (computer science)Boss CorporationScripting languageTheory of relativityState of matterComputer virusRule of inferenceDiscrete element methodConnected spaceInternet service providerFraction (mathematics)Error messageOrder of magnitudeStack (abstract data type)SequenceBuffer overflowDefault (computer science)Software testingDynamical systemComputer fontPeripheralEstimatorMatching (graph theory)NeuroinformatikMaxima and minimaMixed realityComplex (psychology)Cycle (graph theory)Optical disc driveGame controllerField (computer science)NP-hardInstance (computer science)FrequencyData storage deviceBuffer solutionSampling (statistics)Noise (electronics)Independence (probability theory)BlogFamilyOverhead (computing)Term (mathematics)Office suiteLevel (video gaming)2 (number)Programmer (hardware)Latent heatSubject indexingData conversionGraph coloringPlanningTypprüfungGoodness of fitSpeicherbereinigungNamespaceStandard deviationForm (programming)Uniform resource locatorDimensional analysisExpert systemExpressionFigurate numberGroup actionAlgorithmProduct (business)Likelihood functionWave packetGoogolBit ratePropagatorVector spaceComputing platformRun time (program lifecycle phase)Electronic design automationProgramming languageCausalityRoundness (object)Direction (geometry)PressureFlow separationBenchmarkReduction of orderOcean currentUniqueness quantificationUniformer RaumProgram slicingSign (mathematics)Moment (mathematics)CoprocessorBasis <Mathematik>Point cloudAssociative propertyDivisorRevision controlUtility softwarePrice indexAssembly languageWhiteboardCAN busAlgebraic closureFood energyComputer configurationFerry CorstenLink (knot theory)Multi-core processorRadical (chemistry)MathematicsWordSubsetLabour Party (Malta)Information overloadVideo game consoleWeightState diagramExistenceInternet der DingeComputer hardwareArmElectronic data processingMetric systemEnumerated typeJava appletTelephone number mappingPerfect groupSemantics (computer science)Visualization (computer graphics)Software bugLambda calculusInterior (topology)Quantum stateThread (computing)Thomas BayesConformal mapPlastikkartePositional notationMetaprogrammierungEinbettung <Mathematik>Logical constantCache (computing)Code32-bitOrder (biology)Chemical equationLinker (computing)Array data structureIterationCountingComputer animation
Transcript: English(auto-generated)
Is it on now? Yep. So, okay. Welcome to this session titled C++ for Small Devices. I need to start by disclosing that the title is a bit off. The first version that I pitched about a talk here was
very much more low level for a certain architecture with a certain compiler to see what certain language constructs does to your assembler and how you can do that. Not knowing anything about the target audience though, it's sort of become more generic. It's more of a C++ in an embedded context talk. To talk about things in general and what to consider
and techniques you should take with you regardless of your architecture. So, if you came here to see a lot of code, I'm sorry to disappoint this time. But no one is getting up and leaving. So, we can continue. Who am I? And what do I do?
My name is Isaac Steve. I'm from Gothenburg, Sweden where I work for a company called Vinter. We work in the Internet of Things and machine to machine segments. And we work with a quite wide range from small sensor devices up to more higher end data processors and aggregators from devices. Then from there we push stuff into the cloud and do monitoring.
But that's for other sessions, not for the scope. That's me and who I work for. But who are you? Who am I speaking to? Just a raise of hands. If you can raise your hands if you are here today. Great. An audience that can raise their hands and are not afraid to do so.
So, more interesting questions. How many of you are not from the Oslo region? Oh, that's almost half of you. Okay. How many of you are not from Norway? Okay. The people who weren't from the Oslo region. How many of you are doing embedded development weekly or so?
Half. Okay. If I extend it to monthly. Okay. How many of you do it in C++? Occasionally. Not maybe all the time. Okay. That's good to see. Let me just talk a bit about the embedded domain where we work.
As I said, it's a pretty wide range of things we do. This means that in everyday work we get to work with 8-bit bare metal devices up to more multi-core application processor running Linux with Gibbs memory. So, it's a pretty wide span of embedded developments.
In this range, we are predominantly a C++ shop. For most work we do, we tend to pick C++ because of speed, efficiency, and we get the hardware access and things like that. Who knows? Maybe we should migrate to D. I don't know. I'm not yet convinced.
I might be. But as we approach the lower range, then of course there are issues with using C++. But whenever we approach a new problem and a new architecture, we make sure to look at the architecture and what's the capabilities of the architecture, what kind of tooling is there. So we can make a best tool decision based on information and not hearsay.
I find that a lot of projects make decisions based on hearsay and they end up using the lowest common denominator because that's how things are supposed to be done, supposedly. This is what this talk is about, that maybe we shouldn't do it as it's usually done. But then there are quite a bit of C++ embedded developers here,
so I'm going to end up preaching to the choir maybe. But hopefully there are some techniques and tricks I can give you that you can take with you. So this is the session outline, talking a bit about the myths. There aren't many real, total myths if you take out the Stack Overflow trolls from the picture.
But in everything you hear, there is maybe a grain of truth and we need to address it in some way. After we've talked about a number of different areas of embedded C++ and where you need to think a bit about it, some people say, why bother, I'll just stick to C. I don't expect to spend a lot of time on why C++ is a good language
in a C++ track with this many C++ developers, but just for the record, we will spend a short time on that. More importantly, though, how does C++11, and to some extent 14, make C++ an even better language to use for embedded development? Then I don't know where we will end up time-wise,
but maybe if someone has an experience to share or want to state something, there might be time for that, and of course a wrap-up. So the myths, the facts, and the things to consider. Let's start with these. First, two general questions. C++ is a bad fit for embedded development. We hear that all the time,
and we make decisions based on that at face value. We shouldn't. Very poor or non-existent C++ compiler support for some targets. This is a real issue. We need to talk a bit about that. And then some C++ features that are repeatedly called out for being bloated and making C++ unable to use. RTTI exceptions, templates, virtual functions, and STL.
As we talk about these five things, it's a recurring theme that whatever we call out as a good practice for embedded development is really a good practice for all C++ development. Just maybe the case that in embedded development, we pay a higher price for not doing everything we probably should be doing.
But let's start with the first one. C++ is a bad fit for embedded development in a quote and with a period. That's purported to be a fact. We have to stop talking about embedded development like it's a narrow field. We work with 8-bit devices and multi-core ARM9 devices. That's not the same thing.
We have to realize that our higher-end devices now, they have the same computing, the power that a PC had 15 years ago. And the facts you hear about embedded development are usually from that time with a total different performance. So, yeah, we have embedded targets that fit on a match head and some that you need a truck to load into systems.
So whatever you hear, you need to stop and ask yourself, so, okay, this fact, if it is a fact, does it apply to me? Does it apply to my domain in the architecture I'm working? This question isn't usually asked. We just take that as a fact. Oh, we can't use C++. Bummer.
Poor or no C++ compiler support then? Well, this happens. Support is not surprisingly not as strong on all platforms, on all targets. It depends, of course, on how many are using it and how much time has been spent on a compiler to tweak it for the platform you're using. The point is that you need to find out for yourself
also in this case, because things are improving. Compilers are improving. If the compiler was bad 15 years ago, it can be shining now, but you need to figure this out or find this out. The less mainstream your architecture is, well, it's more likely maybe that people aren't really contending what's being said about it,
so we continue to use what we've always been using. Also, of course, if a platform isn't mainstream, it's a higher likelihood that you don't have a C++ compiler. But you can invest just a little time to check the market, Google it, history something. If you find something, spend some time to benchmark it and see, does this work? So dispel the myths by finding out for yourselves
if it feels a bit fishy. But in some cases, it turns out to be true. So what do we do? Well, okay, this might be a long shot, but it's always worth considering. Why is tool support lacking for your target? Is it an old target? Is it due for a refresh? Is it a niche target that was picked at some point,
but there are now more general devices you can use to solve the same problems? Where is the rest of your industry moving? If you're in an industry and other companies are moving technically to other platforms, where are they? Do you maybe follow? I'm not advocating that you go home and re-spin boards
so you can be able to use C++ because your current architecture doesn't support C++. But as software developers for our production work, we tend to work a bit like this. We stick to what we know. We spend our evenings figuring out this new Apple code and learn programming language and stuff. But for production work, we do what we used to do,
and so do electronics designers. If they picked an 8-bit device 15, 20 years ago, they might prefer that because they've always worked with it. But should you? Is that a good idea? If a refresh is coming up, there are a number of other factors to talk about. Usually this is unit cost. How much does the microcontroller cost? But there are so many other costs to factor in here.
Tool and language support is one of them. Development time compared to realistic sales forecast is another thing. We shouldn't save 10 cents on a microcontroller if it costs us weeks and weeks of development time. A week of development time for a Norwegian engineer or a Swedish engineer, you get a lot of microcontrollers for that development time.
So if you aren't selling tens or hundreds, thousands of units for what you're doing, maybe it doesn't make sense to buy microcontrollers on a budget. So when you are designing new things or changing existing architectures,
remember that the case for 8-bit devices is not quite what it used to be. We work with a lot of devices, a broad range, and we create PCBs. And for the majority of our new designs, we're more or less standardized on ARM Cortex devices, 32-bit ARM Cortex devices.
Even at the low end, these have a competitive pricing. They have good tool support. They are fairly easy to work with. If you look at companies like Freescale and Energy Micro, we have to mention Energy Micro in some in Norway, even though they've been bought by other companies. Thank you, Syllabs. And NXP, you get 32-bit MCUs
that cost the same as an 8-bit microcontroller. Maybe sometimes you can't compare this apples to apples because it's peripherals and you need to, but we find that it costs about the same. The important thing is there's much larger options today to create embedded targets,
and we should be able to benefit from that. Okay, this is a bit sales pitchy, but I am not a salesman. I'm just a happy customer. So take this home with you and consider why your architectures look the way they do. So enough of the architecture and the compiler support. Let's say we have an architecture and a compiler that's decent. So let's start talking about some C++ features.
RTTI is the first one. This is the case where mostly what you hear is closer to facts than a myth. RTTI provides you with tools such as TypeID and DynamicCast that makes it easy to develop solutions
where you can do stuff depend on type. But it does so at a quite prohibitive cost for a lot of targets, especially so in run time. The cost is twofold. How many of you are familiar with RTTI, unless reading one slide?
Okay, familiar as in how it works. Okay, not as many. So it's two costs to this. The first one is RTTI makes every class that has a vtable, meaning at least one virtual function, gets adorned with information about this class, including the class name and information about base classes, and this adds up to quite a bit.
And it does this even though you're never invoking TypeID on a class. So you end up paying a cost even though you're not using it, and that's not the idiom we are used to in C++. We want to pay for what we use, right? The other thing is the performance impact. This TypeID or DynamicCast,
they're implementation-dependent, they're performance, and it's usually pretty bad. So this is why you're often here and you often should compile with FNoRTTI. However, type info can be good to have, and your design might benefit from having type info,
and there are alternatives that you can alleviate this without paying the standard pretty high rate. You can do it by just cherry-picking the features you need, because RTTI is a big toolbox. Usually you don't need everything. Pick what you need and implement it for the classes you need, where you need it. A good example of this is LLVM, LLVM compiler.
Are you familiar with that? Anyone using it? One hand like this? Okay. LLVM, they have banned RTTI in their coding guidelines. Their compiler, they do lots and lots and lots of type checking in their code,
and they found out that using RTTI just makes it dreadfully slow to compile stuff. So they ended up implementing their own template functions for the certain features they needed, and actually this comes from Stack Overflow, but it's written by Chris Latner, one of the LLVM backers from Apple, so I trust what he says,
but I would like to see some numbers. They estimate a 5 to 10% reduction in code size because they did this, and more importantly, several orders of magnitude, I would really like to see the numbers here, but it looks good when I read it, from their own LLVM template versus the dynamic cast compared to null pointer. Having said that though,
there are cases where we are using RTTI out of the box. If you have a more higher performance target and performance isn't maybe the main factor of your solution, it's a solution that comes out of the box and you don't need to implement it yourself, and there are benefits to that, but it's very few cases where we use RTTI. Mostly kill it, F no RTTI.
Exceptions. As embedded developers, we've been taught to shun exceptions. Don't let them into your device. We add F no exceptions without thinking twice about it in most cases. I mean, we as a domain, not we as where I work, we try to think about it. We should think about it because we need to think about if we add F no exceptions,
what are we really throwing away and why are we throwing it away, and most importantly, what are we paying for the alternative to exceptions? It's not at all a myth that exceptions have a cost, though. For starters, exceptions rely on some sort of RTTI information to work.
You don't need to compile with RTTI for exceptions to work, but exceptions need to be also adorned with some form of type information. The problem is if an exception is thrown down into the subsystem and it propagates up, and maybe at the subsystem boundary you have an exception handler, there can be one of many exceptions
maybe thrown out of that subsystem, so the handler needs to figure out this is this catch or this catch or this catch that's supposed to do the work. That takes type information. In the case of exceptions, though, this gets adorned on exception types and not every type, and so it's not as expensive. It's also absolutely not as expensive as it used to be in initial exception implementations.
Another thing is that we have a dependency on the IO library. What happens when we have an unhandled exception? We get something printed out in the console saying unhandled exception of type. And therefore we need IO library for this diagnostic output.
That's bad. We want to get rid of that. And we have an implementation-dependent speed penalty whenever no exception is thrown because we need to handle stack unwinding in the case of an exception. Now, at least in the case of GCC, and I don't know other compilers, this is optimized very close if not to zero by other techniques called table-based exception handling.
But these tables for exception handling, they also add to the size then. The first one here is hard to do something about. If you're going to figure out the type, you need to add information about the type, and that's going to take up space. It's not fair, though, to compare code that you compile with exceptions to just compile without exceptions
because you need to consider the alternative. We'll do that next slide. First, no, not next slide. First, we want to see the IO dependency and the runtime speed, what we can do about it. IO dependency, as I said, we work with semi-hosted environments.
I suppose you do as well. You can print stuff in your target and it magically appears in your development machine, and that's good to have for some things. But allowing exceptions to print out diagnostics when they are unhandled, it's just not worth the cost of bringing in the gigantic IO library if you aren't going to use it for anything else.
Good news here. Since GCC 4.8, if you build it yourself, you can configure it with disable libstdcxx or both, and this will just cut out the de-mangler, the DIN string utilities, and the IO streams, and this will reduce the size of your exception handling, or, well, your library for your exception handling
quite a bit. There's a warning in order here, though. If you do this, you now have a silent death behavior. No one is really telling you what's going on. If you have an unhandled exception, it's just finito. So you need another way to find out when you have an unhandled exception.
So exception handling overhead, then. We need to reduce this. One way to do this is to tell the compiler what functions won't let exceptions propagate out. Now, propagate out is the keyword here, because we don't know if they won't attempt to throw or not, but if we tell the compiler an exception will never propagate out,
we will go straight to std terminate, and the watchdog will have to take care of it if it happens. Before C++11, we do this with an empty exception specification, the only exception specification you should use, but it's good in these cases. In C++11, this is still supported,
but now deprecated in favor of noexcept. Noexcept, when I saw it first, was just another name for an empty throw specification, but it's not, as we learned in Niko's talk, for instance. Also, have a look at Google's Scott Meyers talk, an effective C++11 14 sampler.
He referenced this talk in his own talk yesterday, and it's really good. It talks about three different things for one and a half hour, especially noexcept. There's also a draft chapter of his upcoming book that's about noexcept and stuff like that, so lots of good information there if you want to understand how this can control exception behaviors.
So we have to say that the actual gain from doing this varies by compiler, but if you tell the compiler that noexcept is never going to propagate out, there's no use for the compiler to set up exception handling and stack unwinding. That isn't ever going to be used, because we promised.
So we need to take a minute now to consider the alternatives. If we summarize what I said, so we do have a size penalty. We have implementation-specific runtime penalties, hopefully zero if we are using GCC. We are suggested to manually annotate code that won't throw, and if we get this wrong, it's possible that we end up in terminate,
which we may not always do if we are driving a car or something like that. Scott talked, I think it was Scott talked yesterday about how if you say noexcept and something still tries to throw, you will get a compiler warning. Not 100% sure you can always rely on that.
Might depend on at what level stuff are being thrown. This taken together might be why many developers err on the safe side. If no exceptions, we don't use them. But it's important to consider, as I said, if you do this, what are you doing instead? It's usually quite a bit of error handling code, right? This error handling code has a very notable size impact.
It's everywhere. It's run all the time. We need to check return values. We need to be able to propagate return values to our callers so they know that something didn't work. And all of this happens all the time. It's far from zero runtime overhead. So saying that exceptions have runtime overhead is...
Also, check the actual overhead for your target. Because it varies for target and compiler. You might find that it's low enough to be worth the effort, though. Templates. How are we doing for time? Okay. Templates. The worst alleged offender for code load. This has to be templates.
That's the first thing you hear when people say, I don't use templates because they lead to uncontrollable bloat of my code. And they did early on. Very easily. Almost all the time. They still do, if you do templates badly. Like a lot of other constructs in C++. If you do them badly, you're going to pay a price.
But templates are very important for us in embedded development. Because they can really help us to reduce code as much as possible. Because we can be explicit about what we are using and what we want to have instantiated based on the actual usage. For template members that aren't used,
they won't be instantiated by a compiler. Some compilers did in the beginning. Causing bloat. It caused bad reputation. One important thing to consider is specializations for templates. If we have a buffer of samples that's 100 U-32s and we have another buffer of U-32s that's almost 100,
it can be a win to use 100 in both cases. They'll have that extra space. Otherwise we might have two specializations. Parameter-independent code is very easy when you write a template. Oh, you have a templated type and you do something with that. And then you do a lot of other stuff.
And this code will get copied for your different specializations. Make sure to refactor that out into a non-inline helper or maybe a base class so you can reduce your templates as much as possible. Templates also allow us to trade. We can take stuff from the runtime and do it in the compile time,
which is really good for our performance-critical targets. If we have a development host, we can let it compile for a while if the target never has to do the work. Three things ended up here. That's not really compile-time computation, but it's still important to look at. Dynamic to static polymorphism, that's just standard templates.
Compile-time type checking in dimensional analysis. You can see a lot of examples of that on the net. It's a good way to use templates to assert that your design does the right thing. It's receiving the types it's supposed to receive. If you want to take this further, the technique called meta-programming. How many here have tried meta-programming?
A few hands. How many of you have some sort of meta-programming in a product? No one? Okay. There might be reasons for that. It allows us to do as much in compile-time as our development host can handle, and our sanity can handle. Something is going to hit the wall first, unfortunately.
It's incredibly powerful, but also extremely challenging. Nico talked about, or he pitched Metashell yesterday. I didn't know about this. It looks like a nice tool to take some of the pain out of meta-programming, so I'm going to look at that.
There's also a book about meta-programming by Abrahamson Gurdevoy, sort of the book about meta-programming, and in it they have a separate chapter called Debugging the Error Novel, and that's what you saw when Scott Meyers had these 250 lines of template output when you tried to do meta-programming. 250 lines sounded like not so much, maybe.
Okay. So we trade for compile-time. We move work into there, but we need to make sure to control that, because sometimes with templates we allow the compiler to do work that it shouldn't. It does many specializations just to allow the linker later
to find out that, oh, there are duplicates, and we don't. So you can tell the compiler not to do this right away. We can isolate templates. We can do explicit instantiation, and we can do explicit extern instantiation with C++11, compiler firewalling, different techniques just to make sure that we keep the compile-time bay.
The biggest issue, I skipped that, but it's very important, so I'm going back up. Long compile and test cycles, they used to have a bad effect on quality, because you don't run your tests as often, you don't compile as often, and that's bad, so we want to keep it as low as possible. So it's a powerful toolbox.
We should learn it and apply it, not necessarily meta-programming, but templates can give us a lot in embedded programming. But we have to apply them judiciously, because, yes, they can give side effects, but don't rule them out just because of that. Understand them instead. Virtual functions, not much to say about them.
Virtual functions, you have a vtable in a class to store pointers to your different virtual functions, and, of course, this adds to space, and you have to call your functions through this indirection. It adds to the runtime overhead. But what's the alternative? If you're going to do this, maybe you need some switches for type members, and this also has a cost,
and if you want to call different functions, we usually have tables of function pointers. It's the same cost. So the one thing to say about virtual functions, think about how many you have. Remember that for every virtual function, you're paying a size price in forms of the vtable. But they are an excellent design tool,
so I think we should use them. Just use them better not to stop using them. STL, the standard template library, this is usually banned in coding guidelines for embedded development, mostly because of the T. It's a template library. We don't like templates, so we don't use that.
Implementations of STL have been bad. They're much better now. The biggest remaining issue is, of course, free store allocation in STL. Most of the containers do free store allocation, and that's not good in all environments. It's not an issue in a lot of the targets, as they get more memory and get more...
It's okay. But in most targets, it's a side effect that has to be controlled. We can control it in different ways. We'll look at that. In some areas, it's prohibited by laws and regulations. Can't do it. Well, okay, if we can't do it, we can't do it. But now there's a new container class in C++11 that promises not to do free store allocation,
so maybe then we can use it. I know what I do. I think it's good. Dynamic memory allocation techniques, then. So we can't stop Vector from doing dynamic memory allocation, but we can make it less painful, if it is a pain.
Provide our own allocator is sort of the default trick for doing this. So we can allocate memory out of our own pool, and we have full control over the memory layout and things like that. This is a great way to test out the memory conditions. We have an allocator, try to allocate, and we say there's no memory, and we see if it still works.
One thing, when you work with your own allocators, Scott Myers writes about this in his effective STL book. You need to be aware of allocator conventions and restrictions. It's not as straightforward. There are some dark corners, and you need to understand those. But providing your own allocator is a way to get a handle on this dynamic memory thing.
In C++11, there are some allocator-related features or improvements. I have yet to read up on those. I can't tell you the first thing they're about. But maybe there's something we can use to improve on. One thing we can do, then, with Vector, for instance, is reserve.
We still do dynamic allocation, but we can do it at the beginning and not do dynamic reallocation. I've got some weird echo here now. And, of course, the reallocation is the biggest problem. Also, if we have a vector and we push stuff into it and it continuously reallocates,
that's very bad for our embedded environment. But if we have an idea how many elements we're going to put in this vector, we reserve it up front, there are no more allocations. And that's good. It can be hard to prove, though. If you have a review process where someone points out that's a vector, how do you ensure it's never doing reallocation? Not so straightforward.
But, in many cases, it's a good tool to use. So, if free store allocation is a complete no-no, but we still want to use a collection because we think it's a good idea, std array is a new collection class in C++11, available in Boost since a very long time, but now in the standard.
We'll come back to that. If you then find out that, OK, STL, as it is, is too big, it doesn't work for us, consider the alternatives. There are some fat-free alternatives like microSTL and embedded STL that can help you get the same functionality that you need for a much smaller footprint.
I even found a couple of days ago, I haven't even downloaded it, but I found a library called ETL that claims to provide C++14 features, including make unique for 8-bit AVR controllers. That's interesting. I need to see what that's about. I'm not specifically endorsing any of these. You need to pick based on your needs.
But the important thing to remember is that there are lots of alternatives, and it's good to code to a known interface, especially when you want to recruit, if you haven't invented your own container interfaces and stuff like that. So, there are lots to be gained here. Also, finally, I think you must have pretty specific needs to justify writing your own collection classes in 2014.
That is a problem that has been solved right now. So, okay, all these issues, all these things to think about, why should we bother? Again, this is just a single slide for the record. But constructors and destructors, we can get more controlled object creation.
We have resource management with the dry, which is very good to have. We can avoid use of half-constructed objects in a better way. We don't allocate struct memset to zero or forget the memset to zero and set some members and then pass it along. Initializer lists make things very good.
Next thing, inheritance, virtual functions, information hiding. This helps you to maybe have different motor classes. You can inject your own test classes and things like this. Of course, you can do this in C as well with link time walking and different techniques. But it's just not as easy to work with as C++ is.
Better type safety is one of the bigger wins. Better type safety means fewer critical bugs in my mind. I'm sure I can go into some other session here and talk about type safety and they kill me. But in my domain, I think that type safety is a really good thing to have. And it reduces the number of field upgrades we have to do in the bugs.
Operator overloading can make code much more clear than having functions that operates on structs and things like that. And it can be more efficient. Seems that when we talk about operators and efficiency and inlining, everyone mentions Qsort versus sort. It happened twice yesterday and if you Google this, it's the first couple of hits, sort versus Qsort.
So it looks very narrow, but it's actually more widely applicable than that. Operator overloading, as I said, can make code more readable. But we can also go overboard. There are some clever tricks with operator overloading that makes code totally not understandable if you don't have the full picture.
So we need to be sure not to do too much of it. Yes, so C++. If we don't think that C++03 floats our boat, C++11 introduces quite a few things that makes it a better language in my mind.
And I struggle a bit about this because I looked at the different new things in C++11. I found this is good, this is good, this is good, this is good. Then, okay, a lot of it is good, but why is it good in an embedded context? This is supposed to be about embedded development. But we will look at that for a couple of different things.
Also remember Scott's talk, ability to increase performance, is key to us. And there are plenty of things that help us increase performance in C++11. Let's start with a small one, albeit could be good to have. It's quite common that we want to allocate memory on a boundary.
And usually we do this by overallocation and adjusting the pointer so that we end up on the boundary we need. But it more or less requires overallocation to do it. It's now supported with the lineas and the lineof. So we have language support to say that I want to create this memory and I want it to be aligned. And that's it, just do it for me.
The standard only supports alignment up to 16 bytes. So if you try to align bigger than that, let's say bigger cache lines, it's not really standard conformant. So you might want to go back to the old behavior. But it's still good to know about. Aligned storage is a template in the library where you can create uninitialized storage.
It provides you with a pod type where you can store a number of objects with placement new. And where you call explicit destructors yourself to manage memory in a known memory area. I've only started to use this. There might be some gotchas, but it seems good, seems to work.
It's still an array. I've mentioned it twice, if not more already. It's a collection, doesn't use the free store. There's no risk of reallocation at run time. It has a fixed size so it can never grow. It's more efficient in some cases because you have direct element access.
Elements are stored within the array, not through a pointer like in the case of a vector. It's a good replacement for CRAs in a lot of cases. It can be initialized with initializer lists, so can CRAs. But STL classes previously couldn't be, so there was an overhead to manually push data into them.
It doesn't decay into a pointer, unless you really tell it to decay into a pointer. Which means that you know what's an array and what's not. It supports standard container operations, so we can use algorithms and checked iterators. This helps us get better code than writing force for arrays.
We also have balance checking when accessing elements with that. No free store allocation, you can use algorithms, better code. One thing about them is that size is part of the type. So code gets a bit more verbose when you use arrays, when you want to pass them somewhere.
So you might want to type this into something better. Constexpr is a really good new thing that we can use in embedded. It's generalized constant expressions and more approachable and easier to use than metaprogramming. Albeit quite restricted, but it's a good start in C++11.
Whatever we constexpr is evaluated at compile time. So the result of that can be just runnable directly by the compiler. It doesn't have to live in the program. This is not just for functions, we can use constexpr for objects as well.
We can have constexpr constructors and constexpr members. So we can create objects and call functions on them at compile time. In C++11 it's rather limited, we can only have a single line of executable code. You can't do much with that, but you can use the ternary operator. So you can implement recursion with a single line. It's extended in C++14, so you can have an arbitrary number of lines.
You can't call anything else than a constexpr function. And you can only reference constexpr globals. If you have a constexpr function, you can also use it at runtime. So you can use it in compile time and at runtime. But that's less of a benefit.
I think the compile time feature of constexpr is what makes it good. Explicitly deleted functions. Scott I think talked yesterday about explicitly defaulting and deleting constructors and copy constructors and things like that.
We can use it for more than that. We can use it to create more solid interfaces. We can delete overloads that are not wanted. If we have a function that takes a double, that function is going to be callable with an int through implicit conversion. But if we say that can only be called with double for an int max t and we delete that, the compiler will know that, okay, that's not allowed.
I won't let that happen. This is also a more expressive way, I think, to control copying and moving of objects. Previously we had to move the copy constructors and copy assignment constructors into the private part and not define them.
But then they end up usually in different parts of the source file and it's hard to get the picture, what's the copy behavior of this class. Hence I think the boost non-copyable thing that people are using sometimes. With delete, we don't have to do that. We can put them together and make it more readable.
It's a small thing, but still it allows us to write more understandable code. I wrote here on my to-do list, some people have a trick where they have classes, they allocate and they know this class is never ever going to be destructed. The destructor is not going to be called because I put it on the stack or the heap when I start and I'm never going to return because this is an embedded system.
I have an infinite loop further down. So they edit linker scripts to remove the destructor sections to save space. If we delete the destructor, we should be able to just tell the compiler, don't bother generating this destructor because I won't ever be using it, I promise.
I asked Scott Myers about this yesterday and said, yeah, probably, but also he didn't know. It shouldn't take too long to find out, but it hasn't bubbled up on my stack just yet. Move semantics and perfect formatting, this is a huge topic. I'm not going to talk much about it. The main benefit for us is more performance.
That's really good. Scott Myers talked about an example where they had a 10 to 40% speed up just by recompiling with C++ due to the move semantics. Wow, I want to see that reference, but it sounds really great. We can avoid excessive copying. We don't want to do much copies because it takes time, it's expensive to do.
And I think this strengthens the case for usage of STL. If we have algorithms and an array and it supports move semantics and the type we put into it, important to remember, supports move semantics, we're going to get a very nice speed up from that. But it's important then to read up on the tools regarding what disables move semantics.
And that we use that move to actually tell the compiler, it's okay, you can steal this, go on. Usually when people talk about move semantics, it's talked about as a great feature for library writers. Well, this is mostly because most of this belongs in libraries.
But there are two things to this. Someone talked about foundation libraries yesterday, that there are really two kinds of libraries. There's a standard library and there are foundation libraries. In our embedded domains, it's not uncommon that we create our own foundation libraries for our domain, for motor control or whatever. So we do some sort of libraries.
And it's also, for other reasons, a great feature for embedded developers. Just to make sure that we do it as efficient as possible in our sometimes performance constrained architectures. Null pointer. Didn't quite get null pointer when I first saw it. Is it just because C++ wants its own name?
Doesn't seem like a big deal. And of course it's not the biggest improvement in C++11 maybe. But the important thing is that it's its own type. It's a stud null pointer T and it's a constant. It's not null defined to something that could be void star zero or zero or zero L. We know what it is. It's constant and it's the same everywhere.
So we don't have the type conversion and overloading ambiguities that we usually did with null pointers. F of null is ambiguous. It can be an int or long. We don't know. F of null pointer is not ambiguous. It's a type. Being a type we can also specialize templates for null and we can forward null.
Maybe not as... I mean, what's the main benefit in embedded? I don't know. But we can write more type safe code and that's always a good thing for us. Smart pointers. Smart pointers is a contentious topic even outside of embedded. Are they really adding value?
Are they just not adding noise and locking behavior? We need to view them to be more than garbage collection enablers for memory. We can actually be arbitrary lifetime managers of resources. Arbitrary resources. We can control their behavior at creation by using a custom allocator to allocate other stuff that isn't necessarily memory from the heap.
We can use a custom deleter to tell what's supposed to happen when it goes out of scope. Make shared and make unique are the recommended practices to use for performance. But if we need a custom allocator and a deleter, sadly we can't use these because you can't pass both to any of these.
There is allocate share that takes an allocator but then your type has to be deletable with just a delete of the templated type. And that's not what we are looking for when we are trying to handle other resources with this. Beware of oversharing.
So I think that share pointers are useful in more cases than people initially give them credit for. But we need to think about a couple of things that happens. I've been guilty myself of overusing this shiny new tool. We should think of it as a tool for indicating or passing ownership of things.
If we're not passing ownership, it's better if we pass a const ref to the share pointer or a const ref to the underlying data. If we know that lifetime exceeds the function we're calling. So we only pay the cost of this reference counting where needed because reference counting can be very expensive for us. Another thing, and I'm not sure that this is a thing with standard C++ now, but it's bitten me with boost share pointers.
If you use it on some targets down in the bowels of the share pointer, they determine if they're going to lock or not. We used this on a cold fire target when it wasn't locked. So we had very weird behavior and we had to debug to find out that lock was a no op. And it all makes sense in a very embarrassing kind of way.
There are very good utility, as I said, to share resources. But the cost of share pointers must be understood and you need to consider it before use. Especially if you have time critical access to data. It's a lock there, so watch out. Unique pointer is applicable to a wider range.
Unique pointer doesn't support copying or assigning. So this means it can take this sharing overhead out. And it protects your data from inadvertent sharing. Since the copy constructor and copy assignment are not allowed or deleted. Unique pointer supports arrays, share pointer does not.
So it will delete the array type. I added this after last day's talk. I consider this to be an out of the box alternative to Andrei's scope exit solution. He can push a lot of different things on the stack. But this gives you lifetime management and you can specify what's happening with the deleter.
This was Niko's point. Andrei said it would kill it. It wasn't really killed. It is a matter of taste, just like Andrei said. But this is a tool you can use to manage the lifetime of more than just heap memory. And that's what people tend to miss. Where are we time wise? Oh, okay.
Strongly typed enums. So I don't know, but when I work with embedded code, the further down I get in performance, it seems like enums are more common. I don't know if that's your experience. Maybe it's predominantly worked on by C programmers who have used enums more.
And enum is a more common idiom in C. I can only guess. But I think the strongly typed enums is a good thing. In C++11, because all style enums are not types. They implicitly convert to int, causing all kinds of weird behavior. We can compare equal for enums based on their index or based on their enum value.
And that doesn't always make sense. We can get really, really strange things. And the names within the enum, they are hoisted into the surrounding namespace. So we can't have the same enum value into enums. Strongly typed enums fixes these for us.
We can also specify the underlying type, which can be important for us when we want to control layouts and things like that. Controlling a type, I didn't notice, but apparently controlling a type has been available since 2005 in Visual Studio. And GCC has been able to do it for a while.
But with compiler extensions in a non-standard kind of way. Also, just not too long ago, I switched a codebase I was working on to start to use strongly typed enums. And I found a couple of issues because of this implicit conversion and how this int value was used in some not very intuitive ways.
So I've already seen improvements from using strongly typed enums and I encourage you to look into them. User-defined literals. I haven't used these at all, I must admit. But it seems like a good idea.
In embedded development, we work with a lot of different units and we need to make sure that we handle them in the right way. It's not uncommon that we get an int and there's a comment or the int is stored in the variable that has some homegrown Hungarian notation trying to explain what unit it is. And someone replaced that value with another unit higher above and you're just lost.
User-defined literals allow you to specify the units much better and in a safer manner. As I said, I haven't used this at all. Has anyone of you used user-defined literals? No one? Are you aware of them?
I see a few nods that I expect, but not... Okay. It's definitely valuable though. We've seen some pretty spectacular failures from failing to use units the right way. Like mixing the imperial system and the metric system is not a good idea if you're orbiting Mars. You might calculate the altitude wrong and hit the surface.
Uniform initialization, this is pretty big related to type deduction and you heard Scott's take on type deduction. The biggest thing I think here is that it prevents narrowing for us. That makes us able to write better code again.
As I showed, we can use it to initialize STL containers now. So we don't need to create a vector or an array and then push data into it. You can just do it in one invocation. And you can, for your own container classes, should you write them, you could use the initializer list to benefit from this.
There's also a gotcha that I wanted to show you. Scott showed you this yesterday, but it didn't quite tell you the implications. But if you do, the vector will contain 100 elements. With the usual weight, 100, you will get 100 default constructed elements. If you just say that, oh, uniform initialization is the new thing, I'll just change that to curly braces,
you will get a single element that is 100. So it's pretty different. I would say that the first way here to get 100 default constructed elements, it's bitten me and it's bitten a lot of my colleagues because it's quite an intuitive interface for the vector.
So the change is for the vector, but we need to know what happens if we just switch constructs because this is the new thing to do stuff. So more compactness, efficiency, type safety, that's really good things from uniform initialization. So this slide, what did I forget? Is there something you think I should bring up here?
I added one thing because I really love lambdas and std function and you can write good code with that, but I only use it for the higher end systems, not in the lower end systems because when you create a std function, it will do something on your heap and then it's not as fun anymore to overload new and stuff like that.
But yesterday I learned that you can, if you do it like this, you can get your function closer on the stack instead of the heap. So that's interesting. It could maybe be used for something, but I learned yesterday you haven't had the time to explore it. Is there some technique that I know you in the audience are using from C++11 that you think you forgot this?
Hubert? You just stood a ray of thread races and you get five numbers.
Yeah, yeah. You get the five numbers in memory. If you did a vector of that, you might be able to get the five numbers in memory twice. All right. I didn't know. We need to check into that.
Okay. A bit too long to repeat, but Hubert's point is that there's a difference between vector and a ray and how they work with this initializer list. Also, maybe I should have used a ray since I said it was a better collection to begin with, but thank you.
So, how are we doing for time? We have a couple more minutes. Experience is to share and wrap up. I think I'll do the wrap up and then we can take questions and shake hands and see if someone wants to continue to talk about this. It isn't so simple as to say that C++ can't be used for embedded development. In most cases, it can. You just have to think about what you're doing and how you're doing it.
And that shouldn't really be different from other development. Just how it is. Every argument you hear, you need to consider the pro or con. I mean, even things like standing here and saying, this is good, you should use this. Consider, is it good? Is it good for me? Can I use it?
So find out for yourself. The domain is so big that you can't know where the fact is coming from. Challenge guidelines and decisions. Guidelines have a tendency to live for a long time. Someone decided 15 years ago not to use this or that. Why are we still enforcing that? Are you sure you cannot afford your language features? What are you paying for the alternatives for the language features?
And most importantly, C++ is steadily improving now. We didn't see much improvement up until C++ 11. But now we're 14, 17. Things are coming in faster and there are good things coming. So what are we doing to our practices? Are we sticking to old knowledge?
Or are we able to use new features and new tools in our solutions? I think that's it. So are there questions or anything? Yeah, please.
So it's tedious to handle return values in your code. So how do you do it without using exceptions? Is that the question?
It's an odd question because I'm advocating that you should use exceptions. But how do you use? How do you do it? Well, of course, it has to be dependent on the problem you're working on. I can't give you a good answer. Ove, do you have a trick up your sleeve?
No. I think it's hard. I think it happens too often that we assume that stuff is right. Or that function is supposed to tweak some global variable or something. And then we assert that something happened. But we have no idea if it really happened in the right way. So this is why I think that that type of code should be reduced to a minimum.
And use exceptions for exceptional behavior. I mean, don't end up in Java land where everything is an exception. Because that clearly has too much overhead that we don't want to pay for. So I'm sorry. I can't give you an answer. Maybe later tonight. We're not more creative. Mike. True. Yeah, good point.
So Mike is saying that a common thing to do is to implement new in a way that it points to your own allocator.
I think Mike has an example of this on his own blog. Yep. So Google Mike Long allocation and you will find what he's talking about. True. Thank you. Anyone else? Ove?
Well, we aren't using C++ on 8-bit computers right now. Or microcontrollers right now. Not in the production way anyway. As I said, when we create new designs, we don't see a reason to step down to 8-bit.
We have done some 16-bit because we wanted Texas Instruments chips for Texas Instruments radio chips. But that's besides the point. That was a special case. Also, the 8-bit code we are working on is usually some legacy stuff where we are helping a customer and it's already written. But with this new ETL library with C++14 support for 8-bit AVR, I need to check that out.
Okay. Maybe we are not quite out of time. Any more questions? Yes, please. I don't personally. I have colleagues that do on train systems.
Well, that's what I'm talking about. A lot of products and projects mandate C because they have always mandated C. But we code fighter planes in C++ nowadays. So you can't really say that.
Now, the F35 has not quite turned out to be a poster child for C++. Okay, you are under a regulation. And the regulation states.
Okay. True. But that's also different because I'm aware of regulations are different depending on domain. But if you are in a regulated environment, then sticking to regulations is part of your product value. So if you don't do that, your product isn't worth anything.
Yeah. It's easier to affect an in-house guideline than to pick a fight with the regulation bodies. But I understand what you mean. But as I said, I know people who do fighter aircrafts and train systems in C++.
It's perfectly fine. There are miserable standards for C++. There is the JSF coding guidelines. So it can certainly be done. Okay. I think that's it. Thank you very much.