CPU recommendations?

It's not even easy to _work with_ a good template library. The STL is, all grousing aside, pretty good. It's stable, well documented, provides lots of useful functionality, etc.

Oh, yes, and makes your code entirely and completely illegible, even to other STL familiar programmers. To anyone who's not, it makes the syntax of things so obtuse that they can't even tell what language it's in.

--
Rob Gaddi, Highland Technology
Email address is currently out of order
Reply to
Rob Gaddi
Loading thread data ...

I'm not sure I believe either of those premises (that C++ can generate better source code, or better than C on a small micro) ;). (And I'm glad I left it a few days before elaborating.)

First, I plain don't like C++. Let me be upfront and frank about that. I

*do* like OO, but consider C++ to be a poor/primitive implementation. If you want reasons, I'll start with the fact that, in C++, class declarations (mostly) belong in header files. So much for privacy, eh?

I'll absolutely grant you the fact that significant C++ chops are needed to be able to do it even slightly well. And there's a lot of awful C++ code around. (I'll put the fact that I've seen coders debugging code with *every* header file open, trying to understand where the original overloaded/inherited declaration was, in that basket.) On a desktop machine, I'll happily defer to the guys I work with, who do wonders with C++ every day. But on embedded targets (more my area), I have an aversion to any use of malloc/new, and to any form of late binding. Or indeed of private methods/variables in header files. Amongst other things.

I tend to write (embedded) code in object-oriented (or orientated, for the pedants ;)) C. Plain old C. For me, the design is the key thing, and the code is a reflection of the design. I can do most things (except for multiple inheritance, which is dodgy idea anyway...) in plain C - if it's designed a particular way, I can probably code it - in C or assembler, without loading up the runtime with stuff it really doesn't need to do, and which might fail. Failure is not an option on embedded targets.

I agree with that, but most (well, some) of those details have been ported over to plain ol' C. They don't depend on C++. They're just improvements.

Indeed - and again I'd do the same thing with OO C.

Your honesty noted and admired. For me, C++ is a valiant attempt, but just plain broken. Now - if someone were to come up with a new version of C which understands classes, and (emphasis) *led to clearer code*, I'd be right there. C++ ain't it.

My main objective in writing code these days is clarity, clarity, and yet more clarity. Beyond a certain point, I've learned that a die-shrink is more likely to speed up my code than endless optimisation. Similarly, I've learned that keeping things simple and modular, and using more vertical hierarchy than horizontal cuteness, results in faster and clearer code. Again, it's down to design rather than coding. I admit that we tend to do both things together these days. But again,C++ is *not* (IMHO) a good tool - it's the right idea, but badly done. Time for a revision. Ada, anyone?

Your turn ;).

Steve

--

formatting link

Reply to
Steve at fivetrees

Have you investigated Oberon? The Oberon-07 that I use for embedded systems programming does not include all of the OO-specific features of Oberon-2 or Component Pascal but it also doesn't suffer from the C++ flaws that you mentioned. You will find all of the details here:

formatting link

-- Chris Burrows CFB Software Armaide: ARM Oberon-07 Development System for Windows

formatting link

Reply to
Chris Burrows

Noted.

(Don't be afraid children ... C++ is just a toolbox.)

So you want to code C++ features by hand? Hand coded vtables? How do you code templates?

You can put class declarations any where you like. Of course, if you want to share them among other files/modules, then you need to put them in header files. The struct and union constructucts in C work the same way. What is the problem?

What better privacy can you achieve with C that you are unable to accomplish with C++?

Yep. C++ is a powerful, non-trivial tool-kit with many

*optional* facilities, including compatability with C.

Amen.

For some embedded systems, Malloc/new is convenient at initialization. However, free/delete are not OK for mine (heap fragmentation.) Placement *new* is a great tool.

What in the world is wrong with late binding?

I guess you don't like function pointers either.

There is no need to put them there unless of course there is a good reason to put them there. How do you do this better in C ?

Is that the same thing as using a hammer to drill a hole?

Agreed.

Generally speaking, composition is "better" for many problems, but MI (especially interface inheritance) has its place.

Failure is indeed not an option in most embedded targets.

What runtime stuff are you talking about? RTTI? Exceptions? I don't use/allow either of those. RTTI is just plain evil. Exceptions would be nice if they were implemented in a way that does not include dynamic memory allocation. I have never encountered such an implementation :(

One of the great things about C++ is that the language itself doesn't penalize you for features you don't use.

With more (error prone) work? E.g. hand coding inheritance (function tables.)

Do you mean C++ is broken or it's ugly? How is C++ broken? It works quite well for me.

C++ "understands" classes. Obfuscation can be accomplished in any syntax.

Using C to implement C++ features *decreases* clarity, if for no other reason than more code is required to accomplish the same goal.

Using the same argument, you should be writing assembler instead of C.

Clarity is good. Clarity at the module/file level is possible. Clarity at the inter-module level requires external documentation.

Yep. Something about premature optimisation and evil :)

Yep. Consistent naming/terminology combined with the use of design patterns works wonders.

--
Michael N. Moran           (h) 770 516 7918
5009 Old Field Ct.         (c) 678 521 5460
Kennesaw, GA, USA 30144    http://mnmoran.org

"So often times it happens, that we live our lives in chains
  and we never even know we have the key."
"Already Gone" by Jack Tempchin (recorded by The Eagles)

The Beatles were wrong: 1 & 1 & 1 is 1
Reply to
Michael N. Moran

Although I see there is an ARM Oberon compiler, it's not really aimed at embedded systems (it seems to have been targeted at the StrongARM originally), and there certainly is nothing for other embedded devices. It's an interesting language, however.

Reply to
David Brown

So we definitely agree on the second part - that there are few developers that can write good C++ on embedded systems! I don't count myself as a good C++ programmer - I have not had nearly enough experience with it, and certainly not for real-world projects.

I don't like C++ either, for much the same reasons you don't like it. But that doesn't mean you get some benefits from it.

To paraphrase Churchill, C is the worst possible language, but it's the best we've got. C has so many design faults there is no space to list them here (but I'll mention the obsession with "int", the horror that is the preprocessor instead of decent module support, and the typo-friendly "if" and "switch" constructs). Yet I write code (at least some of which is good quality :-) in C all the time.

Imagine you were to give code a "goodness" rating encompassing the clarity of the source code, the maintainability and portability of the code, and the speed and compactness of the generated object code. I would say that for small embedded systems C++ would let you improve that rating by 30% - it also easily lets you drop it by 90% (a single virtual inheritance will make an AVR run like an 8051, and multiple inheritance will turn your source code into Greek). I would also say that the average embedded C++ programmer (from what I have seen) will hit the average spot - i.e., a 30% drop in "goodness" compared to what they could have done in C.

I agree 100% here. I'd also add that multiple inheritance is a terrible idea. There are a few occasions when it is genuinely better than simply including subobjects in your class - but these uses are far outweighed by the costs and complications of multiple inheritance. (If you are a Python fan, there is one *good* use of multiple inheritance - for mixin classes. But these rely on duck typing, which C++ does not have.)

Again, I agree with you here. In particular, the declaration of private code/data in header files is one point where using C++ classes take a step backwards from C's "everything in the global namespace or static to a file" visibility.

It is possible to write OO in C - I've even written OO assembly code. But you have to admit that C++ can make it a little easier.

Again, I agree - it is possible to do *far* better than C++ in designing an OO language. But C++ is the best we have at the moment, and when used well, it can be better than C. But if I were designing an ideal language for embedded design, which would include OO, I wouldn't start from C.

I can't really argue with you when we agree on most of the points - it's really only a difference in weightings and nuances.

Reply to
David Brown

I'd guess he means something like:

void runMotor(motor *mp) { if (mp->isBig) runBigMotor(mp); else runSmallMotor(mp); }

That sort of thing is perfectly possible. It's not as scalable as using C++ class hierarchies, but on the other hand you have all the relevant code in one place.

In C, your header file might contain:

extern void startMotor(void); extern int motorSpeedTarget;

And the C file contains:

static void runMotor(void); static int currentSpeed;

With C++, your header file has the class definition :

class Motor { public : void start(void); int speedTarget; private: void run(void); int currentSpeed; }

There is no excuse for designing an OO language in which code and data that is explicitly reserved for private use within a class implementation must be publicly exposed in the interface definition. It's bad design in the language, and there are no good, efficient ways to avoid it.

The same applies to things like inline functions - they are critical to getting code quality generated code while maintaining an abstract interface, but it means that these implementation details end up in the interface file (the header). The common "workaround" for this is that your C++ program is actually compiled by a driver C++ file that #include's all the other C++ files in the project!

Of course, you can use abstract base classes - if you are willing to pay the cost.

Absolutely. My point all along is that although C++ has many failings, if you use parts of it, and use them carefully, you can get a great deal of benefit for relatively little cost (the header privacy issue being one of these costs).

Agreed - it's free/delete that is the big issue, rather than malloc/new. But it is much better to have fixed static allocation in small systems

- access to linker-allocated addresses is faster (that's different on bigger processors, where the difference is minor).

Late binding is good if you really need it - virtual method accesses are not much different from function pointers (though there might be an extra indirection hidden). But you don't often need it - and with C++ it's easy to get accidental late binding. On big systems, there are certain habits and rules such as "always make your destructors virtual", and if these are carried over to embedded systems they quickly add unnecessary overhead. The point here is not that C++ forces you to have such overhead, but that it takes a good programmer to make sure they don't do that sort of thing by mistake.

This ties in with what I've been saying - if you know what you are doing, know the costs of using features, and know what's important in the design, then you can get a lot of benefit from C++. Things like RTTI and exceptions give little value for money (or source code clarity or generated code quality), especially in small systems.

It's certainly ugly (look at the template syntax, or the "new style" cast syntax). It is certainly broken in several ways - the lack of a clear interface/implementation boundary is a major point. Templates are broken - there is no consensus on whether these should be in headers or cpp files, there is no language-defined way to control or discover what code is generated or used for templates, and there is no consistency in error checking and reporting when using templates. Multiple inheritance is broken - it shouldn't be there in the first place, or it should be done properly (allowing a "sorted list of triangles" to inherit from both "list" and "shape"). Overloading is broken - there should be a way to handle overloading based on return types, and ways to handle ambiguous overloading. And of course there are mistakes in C that C++ has inherited, like int promotion that screws with overloading.

This doesn't mean that C++ doesn't work - but it could have been so much better.

C++ gives new ways to avoid obfuscation, and new ways to implement it.

Reply to
David Brown

I've now adapted the Oberon-07 ARM compiler to specifically target the NXP LPC2000 family of ARM7 microcontrollers. For details see:

formatting link

-- Chris Burrows CFB Software

Reply to
Chris Burrows

I would have never dreamed that I would some day be defending C++ ;-).

I think you are comparing apples and oranges here. In the C version there is a single instance of Motor, whereas the C++ version lets you have multiple Motor instances. If the singleton instance is sufficient, then the C version is also a valid C++ implementation.

The reason for the private part (as you probably know) is separate compilation. If code in another compilation unit needs to create an instance of Motor, it needs to know how much memory to allocate. In the traditional Unix style compilation model, the information needs to come from the specification (e.g. Motor.hh). If it is in the implementation part (e.g. Motor.cc), then you either need some kind of compilation library, or an additional whole-program compilation step before linking.

--
Pertti
Reply to
Pertti Kellomaki

I realise there is a difference here, but I think the comparison is still valid for two reasons. One is that you very often have a single instance, but in C++ you wrap that instance in a class for which there is only a single staticly-allocated instance. So even if you only have a single motor in the system, the natural C++ implementation would have a motor class in this way.

Secondly, if you have multiple instances of "motor", then the natural C implementation would be to have a struct for private data, and a series of functions that take a pointer to that struct (or an index into an array of such structs, depending on which is more efficient on the target). If you have a lot of publicly accessible data, you may define the struct in the header, or you may split it into two structs - a public struct in the header, and a private struct in the implementation (that makes more sense using an array index rather than a pointer parameter). Either way, you at least are still separating your public functions (included in the header) and private functions (in the implementation file only).

Yes, C code is valid C++ code - but if it is C code, it's not a C++ program.

I know the reason for including the private part - it's still very bad design. It's based on a compilation and link strategy that was aimed at the limited computers of 30 years ago, and is simply not appropriate for a language of C++'s date. There were already better models in use at that time (such as Modula 2) - C++ should have been better than existing models, not worse than the reigning king of bad models (C). This stuff is not rocket science now, and it was not rocket science when C++ was conceived - even if you don't want the full whole-program compilation process (because of long build times). C++ was created by tagging OO onto C without doing anything about the design limitations and problems of C, and the result is a language that is not nearly as good as it could have been.

Reply to
David Brown

That's one way to achieve polymorphism. However, each time you add a new type of motor, you must modify this file by hand, and the implementation of "motor" must be changed.

Having all of the code in one place is only a good idea if the module will never be reused in another project.

Where is the C struct for "motor" ? In the previous C example, runMotor() took a pointer to a motor structure, that is the equivalent of the implicit "this" pointer in C++.

The fact that C++ (just like C) is a static systems programing language, seems like a pretty good excuse to me. Especially when one considers compiler implementation issues. It seems a reasonable trade-off to me, and certainly no different from the C language.

The C version of inlining is a macro. How is that "better?" Again, this is more of a trade-off in compiler implementation complexity.

Inlined functions are an optimization, and generally should be used sparingly, when they are short, simple and unlikely to contain logic errors.

Yikes! Those who include C++ files (not headers) deserve what the get. I don't know how "common" this is.

On the other hand, at least GCC is capable of inlining code that does not appear in header files under some circumstances. A similar benefit is also obtained with whole program optimization passes.

Blech.

Agreed.

How does one achieve *accidental* late binding?

I use pure virtual interfaces frequently. The overhead is minimal compared to the benefits it brings in terms of complexity management and code reuse.

Of course, if I have some portion of a system that cannot deal with the overhead, that is optimized as required using alternate techniques. However, that is a rarity.

That rule drives me crazy... especially when it is enforced by the compiler. However, I find the overhead is minimum and avoidable where needed for optimization.

I don't blame the language for being flexible ... but yes ... writing good software requires practice with purpose.

Yep.

It's no more broken than C.

Template programming is UGLY, but it certainly isn't broken.

Templates/Generics are a great way to reuse algorithms while retaining strict type safety.

I certainly define all of my templates in header files simply because like inlining, it makes sense.

Syntax errors in templates are *fun* to find! ;) However, I *have* become accustomed to it with practice.

I distinguish two types of MI.

1) Interface inheritance (think Java interfaces) - In this case you inherit only pure virtual functions. No data, no overridden implentation.

2) Behavioral inheritance. In this case you inherit virtual functions which may or may not be overridden.

I frequently use interface inheritance. Behavioral inheritance is only broken in the sense that if you are crazy enough to use it as a rule then you will pay.

Containment is almost always a better solution.

Overloading in general works fine. But I agree with you about the return types issue. This is rarely a problem for me, however.

Ah the benefits of hindsight. However, as a practical matter, IMHO C++ is a fine language for systems programming.

:)

--
Michael N. Moran           (h) 770 516 7918
5009 Old Field Ct.         (c) 678 521 5460
Kennesaw, GA, USA 30144    http://mnmoran.org

"So often times it happens, that we live our lives in chains
  and we never even know we have the key."
"Already Gone" by Jack Tempchin (recorded by The Eagles)

The Beatles were wrong: 1 & 1 & 1 is 1
Reply to
Michael N. Moran

When the C/C++ Users' Journal was in print there was a monthly column (by Herb Sutter, was it?) Each month somebody in a notional programming shop would be mystified by a problem, and the Guru would sort everybody out. The lesson I took away is that if you program C++ seriously, and you don't have a Guru around, you're doomed.

Mel.

Reply to
Mel

Correct - but that's only relevant if you are going to be adding new motor types often. If that is something that is only done on occasion, and involves lots of changes throughout the program, then the cost of modifying runMotor() is approximately zero. For small programs, there is usually only one programmer who has a fair idea of how everything fits together, and much of the software is dedicated to the particular task in hand. There is therefore very little benefit in keeping a clean abstract interface and separation of code layers - it adds overhead to the generated code, and it adds overhead to the development (since the programmer must understand, obey, and perhaps modify the interface as well as the code itself). Abstracting code into class hierarchies makes sense if you can make use of them for code re-use, and if it really does save time in development. In reality, classes get much less re-use than people think.

It's a good idea if you want to be able to find all the relevant code in one place, and be able to view it and understand it easily. This is very dependent on the size of the project - for larger projects, you need more rigorous structuring. But for smaller projects, such structuring becomes proportionately more overhead.

That was a different example - one in which there could be several motors of several types.

It is worse than C since you have more private implementation details revealed in the public header. And while I agree that it is vital to the efficiency of C++ that details of a class contents are known to its users at compile time (the obvious case being so that the using code knows the size of the class objects, other factors include being able to inline small methods), I totally disagree that this implies that private implementation details must be in the interface file. That is only the case if you rely rigidly on the traditional (i.e., old-fashioned) compilation of individual modules to target object code, and then link them together afterwards.

No, the C version of inlining is inlining, at least with modern compilers. So in this case C is just as bad as C++ - I didn't say C was good, just that C++ was even worse than C in some cases.

Inlined functions were introduced in C++ as a way of reducing function call overhead for small methods so that it is practical to use the abstraction of an accessor method without the cost. They are to be used regularly and freely, albeit with a little care. Where possible, you should let the compiler figure out if a function should be inlined or not - use "inline" as a hint. Used properly, they lead to smaller and faster code. If you don't use inlined functions for your classes, you will either force class users to access data directly, or generate terrible object code (especially on small systems).

I have certainly seen it used. Careful use of namespaces (or at least top-level names - class encapsulation helps here) can make this reasonably safe. It can greatly reduce the overhead of module structuring since the compiler can then optimise across module boundaries.

Yes, gcc can do interprocedural optimisation across modules - *if* you are using C rather than C++. This can lead to significant savings in code space and run time for some types of program. But the relevant flags don't work yet for C++ - this might have something to do with C++ being hideously complicated to parse.

You get it by having virtual functions somewhere in your class hierarchy without thinking about it. A danger of overdoing OO is that you lose track of what your code is actually doing, and where the different parts of the code are to be found. This is very much a case of writing bad software in C++, rather than a failing of C++ itself - but C++ can make it so easy and tempting to write confusing and tangled hierarchies. I've seen programs that are incomprehensible because they are so full of "factory" classes, "delegater" classes, "proxy" classes, "single instance" wrappers, and so on that it is impossible for anyone but the author to figure out what is going on. All the classes seem to do is construct instances of each other and pass control back and forth.

It's all a balance. The danger with C++, especially on small systems, is that it is easy to get seriously out of balance.

As has been said in this thread, C++ is just a tool. But it's a tool that is unnecessarily hard to use well.

It is marginally more broken than C - but yes, C is broken too. C++ missed the opportunity of fixing some of C's failings.

Agreed.

Reply to
David Brown

In a very true sense we still have the computers of 30 years ago, they are just a lot faster. Linux and OSX are really not all that different from 1970's Unix.

Earlier in the post you write:

I think the same applies to operating systems. A fancy compilation system may be way cool, but if it does not play well with `make' and friends, it is not a Unix compiler. This is one reason why Modula 2 and Ada have not taken over.

I don't have anything against advanced development systems -- I would love to run Symbolics Genera instead of Linux, and program in Common Lisp. But I also think there are valid reasons why the Unix compilation model has persisted so long.

--
Pertti
Reply to
Pertti Kellomaki

Can you explain why you think that Ada compilers do not play well with 'make'?

The most accessible Ada compiler today for Unix systems -- the GNU Ada compiler, GNAT -- follows the Unix philosophy in that the "Ada library" consists of the source-code files (*.ads, *.adb) plus a compiler-generated information file (*.ali) for each package. I think it plays well enough with 'make', although it does not need 'make' because the module/package system in Ada is strong enough to let the compiler figure out what needs recompilation.

I certainly prefer to just type 'gnatmake prog', whatever changes I have made in the multiple source-code files that make up 'prog', without having to edit a makefile to show that some source file now #includes some other file, as for C.

--
Niklas Holsti
Tidorum Ltd
niklas holsti tidorum fi
       .      @       .
Reply to
Niklas Holsti

I was wondering the same thing myself with respect to Modula-2 compilers.

Exactly. The fact that languages like C need separate makefiles that have to be manually maintained to keep in sync with source code is a disadvantage NOT an advantage. Not only is it a tedious task it is also error prone.

-- Chris Burrows CFB Software Armaide: LPC2000 Oberon-07 Development System

formatting link

Reply to
Chris Burrows

While I fully agree that C's #include mechanism and total lack of proper module control and dependency checking is a disadvantage, it's worth noting that you can do a lot with make, a good directory structure, and a decent compiler (i.e., gcc). I don't do any manual maintenance on my makefiles.

When I start a new project, I use the same basic makefile as always. I modify it a little to match the compiler and target, along with the name of the project, any libraries that are needed, and so on. But I don't list the C files or object files - I use wildcard matching to compile all the C files in the directory. And there is no need to figure out header dependencies - gcc (or rather, its preprocessor) can generate dependency lists itself (the -MDD flag, IIRC). If I'm doing a project using a more limited commercial compiler, I still use gnu make and gcc's cpp to handle dependencies (I also use gcc as an error and warning checker even if it is not generating the target code).

Reply to
David Brown

Oooh, interesting.

I'll be back when I've read up. Gimme a while ;).

Steve

--

formatting link

Reply to
Steve at fivetrees

Michael, all of your responses were cogent, and I'd discuss any one of them at length over several pints of bitter ;). And I'll respond more fully (earlier in my evening). Meanwhile, this one point: I disagree ;).

For me, it's about interfaces. I deal with a 3-terminal regulator as you'd expect: one input, one output, one ground. I read the datasheet from time to time (yes, external documentation [1]), but the interface is my main connection.

So it should be in software. I don't need to know the details of what's going on under the hood (unless I need more detail about my I/O than I understand); I just need to know what it does. I do a lot of Unix sysadmin work; I can configure one .conf file happily while having some faith that the code will do what I ask. I don't need/want to know anything else.

Fundamentally this comes down to good decomposition and good methodologies for interfacing. Some of these techniques are now well-understood ("spaghetti code sucks", "globals are bad"); some less so - learning good decomposition and breaking things down into simple modules with clean interfaces seems to be a hard thing to teach. Or indeed learn.

[1] Trouble with s/w documentation is that it is always out of sync with the code. I'd rather the header file provided the interface and all the details of "how" and "what"... the docs will likely tell me what the originator had in mind for version 0.1 ;).

Laters...

Steve

--

formatting link

Reply to
Steve at fivetrees

Exactly and indeed exactly. Furthermore, exactly.

<

But I *lerv* function pointers ;). I can hide a *world* of sin behind them ;).

No, seriously, I'm scarily into indirection.

It's certainly ugly (look at the template syntax, or the "new style" cast syntax). It is certainly broken in several ways - the lack of a clear interface/implementation boundary is a major point. Templates are broken - there is no consensus on whether these should be in headers or cpp files, there is no language-defined way to control or discover what code is generated or used for templates, and there is no consistency in error checking and reporting when using templates. Multiple inheritance is broken - it shouldn't be there in the first place, or it should be done properly (allowing a "sorted list of triangles" to inherit from both "list" and "shape"). Overloading is broken - there should be a way to handle overloading based on return types, and ways to handle ambiguous overloading. And of course there are mistakes in C that C++ has inherited, like int promotion that screws with overloading.

This doesn't mean that C++ doesn't work - but it could have been so much better > C++ gives new ways to avoid obfuscation, and new ways to implement it.

Reply to
Steve at fivetrees

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.