Reply by Tom Gardner April 20, 20212021-04-20
On 20/04/21 13:09, Tauno Voipio wrote:
> On 19.4.21 21.33, Tom Gardner wrote: >> On 19/04/21 17:38, pozz wrote: >> >>> What do you suggest for a poor C embedded developer that wants to try C++ on >>> the next project? >> >> Choose a /very/ small project, and try to Get It Right (TM). >> >> When you think there might be a better set of implementation >> cliches and design strategies, refactor bits of your code to >> investigate them. >> >> Don't forget to use your favourite IDE to do the mechanics >> of that refactoring. > > I'd like to add: Read the generated assembly code. C++ is > prone to exlpode surprises into it. > > An experienced C embedded programmer would read the code, > anyway ...
Agreed. Unfortunately I've come across too many interview candidates that haven't a clue about the code emitted by a compiler when it encounters a function call.
Reply by Tauno Voipio April 20, 20212021-04-20
On 19.4.21 21.33, Tom Gardner wrote:
> On 19/04/21 17:38, pozz wrote: > >> What do you suggest for a poor C embedded developer that wants to try >> C++ on the next project? > > Choose a /very/ small project, and try to Get It Right (TM). > > When you think there might be a better set of implementation > cliches and design strategies, refactor bits of your code to > investigate them. > > Don't forget to use your favourite IDE to do the mechanics > of that refactoring.
I'd like to add: Read the generated assembly code. C++ is prone to exlpode surprises into it. An experienced C embedded programmer would read the code, anyway ... -- -TV
Reply by Niklas Holsti April 20, 20212021-04-20
On 2021-04-20 2:01, Paul Rubin wrote:
> Niklas Holsti <niklas.holsti@tidorum.invalid> writes: >> You can impose that constraint if you want to: if I had defined >> type Zero_Handler is not null access procedure; >> then the above declaration of the At_Zero component would be illegal, >> and the compiler would insist on a non-null initial value. > > Oh, this is nice. > >> But IMO sometimes you need pointers that can be null, just as you >> sometimes need null values in a database. > > Preferable these days is to use a separate type for a value that is > nullable or optional, so failing to check for null gives a compile-time > type error. This is 't Option in ML, Maybe a in Haskell, and iirc > std::Optional<T> these days in C++.
I'm not aware of proposals for such a thing in Ada.
>> The state of the art in Ada implementations of critical systems is >> slowly becoming to use static analysis and proof tools to verify that >> no run-time check failures, such as accessing a null pointer, can >> happen. That is already fairly easy to do with the AdaCore tools >> (CodePeer, SPARK and others). Proving functional correctness still >> remains hard. > > I know about SPARK. Is CodePeer something along the same lines? Is it > available through GNU, or is it Adacore proprietary or what?
CodePeer is a for-money AdaCore tool, a static analyzer that basically (AIUI) applies bottom-up weakest-precondition construction and pre-post-condition analysis to detect many kinds of logical programming problems, in particular possible failures of run-time checks. See https://www.adacore.com/codepeer. To use it in practice on larger programs, one usually has to guide and help its analysis by writing a certain amount of explicit precondition and postcondition aspects into the Ada source (which CodePeer then verifies as part of its analysis), but that has other benefits too, of course.
> I still have Burns & Wellings' book on SPARK on the recommendation of > someone here. It looks good but has been in my want-to-read pile since > forever. One of these days.
Note that most if not all of the SPARK-specific comments that were used earlier have now been superseded by the precondition, postcondition, invariant etc. aspects in standard Ada, and the SPARK tools have been updated accordingly. I'm not sure if this replacement is complete; I haven't used SPARK. By the way, did you know that NVIDIA has started using SPARK? There's a presentation at https://www.adacore.com/webinars/securing-future-of-embedded-software. niklas holsti tidorum fi . @ .
Reply by David Brown April 20, 20212021-04-20
On 20/04/2021 08:18, Paul Rubin wrote:
> David Brown <david.brown@hesbynett.no> writes: >> For the kinds of code I write - on small systems - I disable >> exceptions in C++. I prefer to write code that doesn't go wrong >> (unusual circumstances are just another kind of value), and the kind >> of places where exceptions might be most useful don't turn up often. > > That's not so great if you call library routines that can raise > exceptions, and having to propagate error values back up the call chain > is precisely the hassle that exceptions avoid. >
If you don't use exceptions, don't call library functions that raise exceptions. If you don't treat "errors" as something mysterious that might or might not happen, which should wander around the code in the vague hope that something somewhere can make it all better, then you don't need silent propagation of exceptions - also known as invisible and undocumented longjumps. When you are programming on a PC, you can be using huge libraries where you know the basic interface of the parts that you use, but have no idea of the contents and what might possibly go wrong. When you are programming for small-systems embedded systems, you /know/ what can go wrong in the functions you call - /nothing/. There are no unexpected or untreated errors - or your development is not finished yet. Any possible returns are part of the interface to the function, whether they are "success" returns or "failure" returns. In this kind of system, you don't have the option of showing a dialogue box to the user saying "unexpected exception encountered - press OK to quit".
> There is a proposal for "deterministic exceptions" in C++ that work like > Haskell's Either monad. I.e. they are syntax sugar for propagating > error values upwards, with the compiler automatically inserting the > tests so you don't have to manually check the return values everywhere. > I guess they are a good idea but I don't know if they can replace the > other kind of exceptions. I don't know the current state of the > proposal but some info and links should be here: > > https://github.com/cplusplus/papers/issues/310 >
They are indeed a good idea. I am quite happy with explicit exceptions or error indications that are part of the interface to functions, and I would be happy with syntactic sugar and efficient compiler support. What I would /not/ use on embedded systems is hidden mechanisms where you don't know what exceptions a function might throw. There are plenty of other attempts at conveniently and explicitly handling errors using templates and classes that typically work as sum types (like the "Either" you mentioned) or a product type (like a struct with a "valid" flag as well as the real result type). I have found it convenient to use std::optional, for example.
Reply by David Brown April 20, 20212021-04-20
On 20/04/2021 00:55, Paul Rubin wrote:

>>> And keep smiling! [](){}(); (That's the C++11 smiley - when you >>> understand what it means, you're laughing!) > > Heh, if that means what I think it means. >
If you think it means anything at all, then you are probably correct about what you think it means. (That sounds a bit cryptic, but it is meant literally.)
Reply by David Brown April 20, 20212021-04-20
On 19/04/2021 23:17, pozz wrote:
> Il 19/04/2021 22:48, David Brown ha scritto: >> On 19/04/2021 18:38, pozz wrote: >> >>> What do you suggest for a poor C embedded developer that wants to try >>> C++ on the next project? >>> >>> I would use gcc on Cortex-M MCUs. >>> >> >> I'm not entirely sure what you are asking - "gcc on Cortex-M" is, I >> would say, the right answer if you are asking about tools. > > I mentioned the tools just as a starting point. I don't know almost > anything about C++, but I coded C for many years. I think there are some > precautions to take in this situation to learn C++ respect learing C++ > as the first language. > > >> Go straight to a new C++ standard - C++17.&#4294967295; (If you see anything that >> mentions C++98 or C++03, run away - it is pointless unless you have to >> maintain old code.)&#4294967295; Lots of things got a lot easier in C++11, and have >> improved since.&#4294967295; Unfortunately the law of backwards compatibility means >> old cruft still has to work, and is still there in language.&#4294967295; But that >> doesn't mean you have to use it. >> >> Go straight to a newer gcc - gcc 10 from GNU Arm Embedded.&#4294967295; The error >> messages are much better (or at least less horrendous), and the static >> checking is better.&#4294967295; Be generous with your warnings, and use a good IDE >> with syntax highlighting and basic checking in the editor. >> >> Disable exceptions and RTTI (-fno-exceptions -fno-rtti), and enable >> optimisation.&#4294967295; C++ (used well) results in massive and incomprehensible >> assembly unless you have at least -O1. >> >> Don't try and learn everything at once.&#4294967295; Some things, like rvalue >> references, are hard and rarely useful unless you are writing serious >> template libraries.&#4294967295; There are many features of C++ that are needed to >> write libraries rather than use them. >> >> Don't be afraid of templates - they are great. >> >> Be wary of the bigger parts of the C++ standard library - std::vector >> and std::unordered_map are very nice for PC programming, but are far too >> dynamic for small systems embedded programming.&#4294967295; (std::array, however, >> is extremely useful.&#4294967295; And I like std::optional.) >> >> Think about how the code might be implemented - if it seems that a >> feature or class could be implemented in reasonably efficient object >> code on a Cortex-M, then it probably will be.&#4294967295; If it looks like it will >> need dynamic memory, it probably does. > > Dynamic memory... is it possible to have a C++ project without using > heap at all? >
Baring a few minor issues and easy tweaks, pretty much any C code will also be valid C++ code. Some people use C++ as ABC (A Better C), taking advantage of a few features like better constants, references (rather than pointers), namespaces, and other points that look like they could work as extensions to C rather than a new language. In general, you avoid dynamic memory in C++ by avoiding "new" or any standard library functions that might use it - just as you avoid "malloc" in C by not using it. There are many standard types and functions that can use dynamic memory in C++, but usually it is quite obvious if they will need it or not. When you want to be more sophisticated, C++ gives you features to override and control "new" and memory allocation in many ways, with memory pools and custom code. But don't try that in the first couple of days.
Reply by Paul Rubin April 20, 20212021-04-20
David Brown <david.brown@hesbynett.no> writes:
> For the kinds of code I write - on small systems - I disable > exceptions in C++. I prefer to write code that doesn't go wrong > (unusual circumstances are just another kind of value), and the kind > of places where exceptions might be most useful don't turn up often.
That's not so great if you call library routines that can raise exceptions, and having to propagate error values back up the call chain is precisely the hassle that exceptions avoid. There is a proposal for "deterministic exceptions" in C++ that work like Haskell's Either monad. I.e. they are syntax sugar for propagating error values upwards, with the compiler automatically inserting the tests so you don't have to manually check the return values everywhere. I guess they are a good idea but I don't know if they can replace the other kind of exceptions. I don't know the current state of the proposal but some info and links should be here: https://github.com/cplusplus/papers/issues/310
Reply by David Brown April 20, 20212021-04-20
On 19/04/2021 23:37, Niklas Holsti wrote:
> On 2021-04-19 22:47, Paul Rubin wrote: >> Dimiter_Popoff <dp@tgi-sci.com> writes: >>> On 4/19/2021 14:04, Niklas Holsti wrote: >>>> ...their HR departments say that they cannot find programmers trained >>>> in Ada. Bah, a competent programmer will pick up the core concepts >>>> quickly, says I. >>> >>> This is valid not just for ADA. An experienced programmer will need days >>> to adjust to this or that language. I guess most if not all of us have >>> been through it. >> >> No it's much worse than that.&nbsp; First of all some languages are really >> different and take considerable conceptual adjustment: it took me quite >> a while as a C and Python programmer to become anywhere near clueful >> about Haskell.&nbsp; But understanding Haskell then demystified parts of C++ >> that had made no sense to me at all. > > > I agree that it takes a mental leap to go from an imperative language to > a functional languge, or to a logic-programming / declarative language. > > To maintain a Haskell program, one would certainly prefer to hire a > programmer experienced in functional programming, over one experienced > only in C, C++ or Ada.
Certainly. The differences between C++ and Ada are much smaller than to Haskell. Some languages mix imperative and functional paradigms. In C++, you can do a certain amount of functional-style coding - you have lambdas, and with templates and generic functions you can manipulate functions to some extent. Ranges and list comprehensions are new to the latest standard, though being library features they are not as neat in syntax as you get in languages that support these directly (like Python or, more obviously, Haskell). Pattern matching is on its way too. You see at least some functional programming features on many interpreted languages too, like Python and Javascript. Going the other way, you get ocaml that is basically a functional programming language with some imperative features added on. But for someone unused to functional programming, Haskell does look quite bizarre. (Though it is not at the level of APL!).
> >> Secondly, being competent in a language now means far more than the >> language itself.&nbsp; There is also a culture and a code corpus out there >> which also have to be assimilated for each language.&nbsp; E.g. Ruby is a >> very simple language, but coming up to speed as a Ruby developer means >> getting used to a decade of Rails hacks, ORM internals, 100's of "gems" >> (packages) scattered over 100s of Github repositories, etc.&nbsp; It's the >> same way with Javascript and the NPM universe plus whatever >> framework-of-the-week your project is using.&nbsp; Python is not yet that >> bad, because it traditionally had a "batteries included" ethic that >> tried to standardize more useful functions than other languages did, but >> it seems to have given up on that in the past few years. > > > Relying on libraries/packages from the Internet is also a huge > vulnerability. Not long ago a large part of the world's programs in one > of these languages (unfortunately I forget which -- it was reported on > comp.risks) suddenly stopped working because they all depended on > real-time download of a small library package from a certain repository, > where the maintainer of that package had quarreled with the repository > owner/provider and had removed the package. Boom... Fortunately it was a > very small piece of SW and was easily replaced. >
Would that have been a Javascript library? It is common (mad, but common) to pull these from external servers (rather than your own webserver), and it would happen every time a webpage with the code is loaded.
> The next step is for a malicious actor to replace some such package with > malware... the programs which use it will seem to go on working, but may > not do what they are supposed to do.
Reply by David Brown April 20, 20212021-04-20
On 20/04/2021 00:03, Niklas Holsti wrote:
> On 2021-04-19 23:19, David Brown wrote: >> On 19/04/2021 18:16, Niklas Holsti wrote: >>> On 2021-04-19 15:22, David Brown wrote: >>>> On 19/04/2021 12:51, Niklas Holsti wrote: >>>>> >>>>> (I think there should be a "volatile" spec for the "p" object, don't >>>>> you?) >>>> >>>> It might be logical to make it volatile, but the code would not be >>>> different (the inline assembly has memory clobbers already, which force >>>> the memory accesses to be carried out without re-arrangements). >>> >>> >>> So you are relying on the C++ compiler actually respecting the "inline" >>> directive? Are C++ compilers required to do that? >>> >> >> No, it is not relying on the "inline" at all - it is relying on the >> semantics of the inline assembly code (which is compiler-specific, >> though several major compilers support the gcc inline assembly syntax). >> >> Compilers are required to support "inline" correctly, of course - but >> the keyword doesn't actually mean "generate this code inside the calling >> function".&#4294967295; It is one of these historical oddities - it was originally >> conceived as a hint to the compiler for optimisation purposes, but what >> it /actually/ means is roughly "It's okay for there to be multiple >> definitions of this function in the program - I promise they will all do >> the same thing, so I don't mind which you use in any given case". > > > If the call is not in fact inlined, it seems to me that the compilation > of the caller does not see the "asm volatile" in the callee, and > therefore might reorder non-volatile accesses in the caller with respect > to the call. But perhaps such reordering is forbidden in this example, > because p is a pointer, and the callee might access the same underlying > object (*p) through some other pointer to it, or directly.
Either the compiler "sees" the definition of the functions, and can tell that there are things that force the memory access to be done in middle (whether these functions are inlined or not), or the compiled does not "see" the definitions and must therefore make pessimistic assumptions about what the functions might do. The compiler can't re-order things unless it can /prove/ that it is safe to do so.
> > >> As a matter of style, I really do not like the "declare all variables at >> the start of the block" style, standard in Pascal, C90 (or older), badly >> written (IMHO) newer C, and apparently also Ada.&#4294967295; I much prefer to avoid >> defining variables until I know what value they should hold, at least >> initially.&#4294967295; Amongst other things, it means I can be much more generous >> about declaring them as "const", there are almost no risks of using >> uninitialised data, and the smaller scope means it is easier to see all >> use of the variable. > > > I mostly agree. However, there is always the possibility of having an > exception, and the question of which variables an exception handler can > see and use. >
In C++ (and C99, but it doesn't have exceptions), you don't need to put your variables at the start of a block. But their scope and lifetime will last until the end of the block. So if you need a variable in an exception, you have to have declared it before the exception handling but you don't need it in a rigid variables-then-code-block structure : { foo1(); int x = foo2(); // <- Not at start of block foo3(x); try { foo4(x, x); } catch (...) { foo5(x); } }
> When all variable declarations are collected in one place, as in > > &#4294967295;&#4294967295; declare > &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; <local var declarations> > &#4294967295;&#4294967295; begin > &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; <statements> > &#4294967295;&#4294967295; exception > &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; <handlers> > &#4294967295;&#4294967295; end > > it is easy and safe to say that the handlers can rely on all the > variables declared between "declare" and "begin" being in existence when > handling some exception from the <statements>. If variables are declared > here and there in the <statements>, they might or might not yet exist > when the exception happens, and it would not be safe for the local > exception handler to access them in any way.
Flexible placement of declarations does not mean /random/ placement, nor does it mean you don't know what is in scope and what is not! And you can be pretty sure the compiler will tell you if you are trying to use a variable outside its scope.
> > Of course one can nest exception handlers when one nests blocks, but > that becomes cumbersome pretty quickly. > > I don't know how C++ really addresses this problem. >
For the kinds of code I write - on small systems - I disable exceptions in C++. I prefer to write code that doesn't go wrong (unusual circumstances are just another kind of value), and the kind of places where exceptions might be most useful don't turn up often. (I use exceptions in Python in PC programming, but the balance is different there.)
> >>> (In the next Ada standard -- probably Ada 2022 -- one can write such >>> updating assignments more briefly, as >>> >>> &#4294967295;&#4294967295;&#4294967295;&#4294967295; p.all := @ + z; >>> >>> but the '@' can be anywhere in the right-hand-side expression, in one or >>> more places, which is more flexible than the C/C++ combined >>> assignment-operations like "+=".) >>> >> >> It may be flexible, but I'm not convinced it is clearer nor that it >> would often be useful.&#4294967295; But I guess that will be highly related to >> familiarity. > > > Yes, I have not yet used it at all, although I believe GNAT already > implements it. > > I imagine one not uncommon use might be in function calls, such as > > &#4294967295;&#4294967295; x := Foo (@, ...); > > I believe that the main reason this new Ada feature was formulated in > this "more flexible" way was not the desire for more flexibility, but to > avoid the introduction of many more lexical tokens and variations like > "+=", or "+:=" as it would have been for Ada.
Sounds reasonable.
Reply by Paul Rubin April 19, 20212021-04-19
Niklas Holsti <niklas.holsti@tidorum.invalid> writes:
> You can impose that constraint if you want to: if I had defined > type Zero_Handler is not null access procedure; > then the above declaration of the At_Zero component would be illegal, > and the compiler would insist on a non-null initial value.
Oh, this is nice.
> But IMO sometimes you need pointers that can be null, just as you > sometimes need null values in a database.
Preferable these days is to use a separate type for a value that is nullable or optional, so failing to check for null gives a compile-time type error. This is 't Option in ML, Maybe a in Haskell, and iirc std::Optional<T> these days in C++.
> The state of the art in Ada implementations of critical systems is > slowly becoming to use static analysis and proof tools to verify that > no run-time check failures, such as accessing a null pointer, can > happen. That is already fairly easy to do with the AdaCore tools > (CodePeer, SPARK and others). Proving functional correctness still > remains hard.
I know about SPARK. Is CodePeer something along the same lines? Is it available through GNU, or is it Adacore proprietary or what? I still have Burns & Wellings' book on SPARK on the recommendation of someone here. It looks good but has been in my want-to-read pile since forever. One of these days.