Another C++ thread

Go To Last Post
118 posts / 0 new

Pages

Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In the thread https://www.avrfreaks.net/index.p... stevech claims that

Quote:
C++ on a microprocessor = driving a car with the emergency brake on.

This has been up here on AVRfreaks several times before. Every time there seems to be a lack of substance to this claim. AFAICR I myself has posted some thoughts about the costs of vitual functions, and I seem to recall a post which talked about template classes. Apart from that the claim is made, but based on thin air.

Could stevech either remind me of threads I have forgotten about, or re-iterate the proof of the claim.

This is about the accusation that C++ by principle, and "overall" produces inefficient code as compared to C.

When the subject is up, we inevitably also end up discussing if there are any merits to using C++ v/s C. That's OK by me. I have no "religous" opinion on that matter. I would be interested in a moderated debate. (I know - there has been requests for a real-word example of C++ on AVRs. I'm working on that. Slowly. And I am not finished yet.)

If we have the latter discussion (on the merits) then please don't use this to dodge, camouflage or derail the former (about the code effectiveness of C++). They are two different discussions, as far as I am concerned. For the former we should be able to be deterministic. The latter is more about opinion, experience, religion and the phase of the moon.

I have posted several longish things on why I see no principal reasons that C++ should be less effective than solving the same problem in C. I am willing to repeat and refine such a rant if you want to.

So, with the flame throwers tucked away - let's try to establish in which situations C++ must produce less effective code than C.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I see a definition problem here, which I guess is kind of the question as well: What do you call C++?
Due to the fact that C++ was an extension to C (which has since that day moved away on its own too), you can pretty much write a "C" program, call it "C++" and compile it. I guess that'll be the "lower limit" on the tests. On the other hand, you can go all out and make the program completely object oriented, with inheritances and all the bells and whistles, and end up very far away from C.
What it boils down to is (as I see it): "Under which circumstances can the compiler be as smart parsing C++ code as it can be with C code?"

I'll be following this discussion eagerly - I'd love to use function overloading (I'm terrible at coming up with names! :P) and operator overloading (PORTB += 7 to set bit 7 of port b high, PORTB -= 7 to set it low anyone? (yes, I know, shifting is almost as neat)) :)
Wish I had some time to actively contribute - I'll see if I can't talk my boss into stealing an hour or two to run some comparative runs with the bells-and-whistles Imagecraft compiler I'm forced to use at work.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A comparison of the technical performance of C v/s C++ should, IMO, be done so that you compare the same requirement cases. Ie, it is totally meaningless to claim that virtual function calls in C++ are more costly than function calls in C. The C code (to be compared with C++ virtual calls) should implement polymorphism. C code not implementing polymorphism should be compared with C++ code not utilizing virtual functions.

Quote:
I'd love to use function overloading

Theoretically, should come with no run-time cost. If I'm not tied up to the lawn-mower and the dog the whole week-end I'll do some tests on avr-gcc.

Now, where's stevech?

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think it was my post about the template classes... tends to be one of my favorite little C++ features.

Perhaps this summer now that I'm away from school for a while I'll be able to get some of my code in order to share ( I'm getting tired of looking forward to the day ). Nothing deterministic and quantifiable here but I've found in almost all cases a difference ( in code size, mainly preamble to main() ) of no more than a few percent in even toy programs, it all but disappears in full applications. This is without the use of new/delete and no virtual functions, fully object oriented however.

It is exceptionally easy to do operator overloading so that you can deal things in a neater manner. Try,

#include 
#include 

struct OutPin
{
  typedef OutPin type;
  typedef volatile unsigned char * const port_t;
  const uint8_t bit_;
  port_t port_;
  
  OutPin( port_t _port, uint8_t _bit ) :
    port_( _port ), bit_( _bit ) {}
  
  type& operator = ( bool _value ){
    if( _value ){
      *port_ |= ( 1 << bit_ );
    } 
    else{
      *port_ &= ~( 1 << bit_ );
    }
    return *this;
  }
  
  type& operator ~(){ 
    *port_ ^= ( 1 << bit_ ); 
    return *this; 
  }  
};

struct InPin
{
  typedef InPin type;
  typedef volatile unsigned char* const port_t;
  const uint8_t bit_;
  port_t port_;
  
  InPin( port_t _port, const uint8_t _bit ) :
    port_( _port ), bit_( _bit ) {}
    
  operator bool() const { 
    return ( bool ) ( ( *port_ & ( 1 << bit_ ) ) == ( 1 << bit_ ) ); 
  }
};
  
int main( void )
{
  OutPin led( &PORTB, 2 );	// Create the led
  InPin enabled( &PIND, 0 );	// Create the input
  led = true;			// Turns it on
  led = false;			// Turns it off
  while( 1 ){
    if( enabled ) ~led;		// Toggle it!
  }
}

This code, simple though it may be, allows you to treat a pin as a first class object. And here is the C equivalent ( keeping the semantics consistent ),

#include 
#include 

#define LED_PORT PORTB
#define LED_PIN ( 1 << 2 )

#define INPUT_PORT PIND
#define INPUT_PIN ( 1 << 0 )

int main( void )
{
  LED_PORT |= LED_PIN;		// Turns the led on
  LED_PORT &= ~LED_PIN;		// Turns it off
  while( 1 ){
    if( ( INPUT_PORT & INPUT_PIN ) == INPUT_PIN ){
      LED_PORT ^= LED_PIN; 	// Toggle it!
    }
  }
}

At -O2, compiling for the ATmega644, with avr-gcc 4.3.3, both programs compile to exactly the same size,

  text    data     bss     dec     hex 
    198       0       0     198      c6

You could, of course, argue that the C++ requires more code, but, of course, the class definitions would just be floating in a header somewhere and never be seen in the user code. Once that's gone I would argue that the object oriented version with operator overloading is clearer, with the possible exception of the toggle, and it would be just as easy to make that a named function anyway.

But the main point is that we have object oriented code, clean syntax and no extra overhead; that is quantitative.

Martin Jay McKee

Edit: Traded bitwise AND for the original ( incorrect ) logical AND.

As with most things in engineering, the answer is an unabashed, "It depends."

Last Edited: Fri. Jun 4, 2010 - 05:14 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

At -O2, compiling for the ATmega644, with avr-gcc 4.3.3, both programs compile to exactly the same size,

I'd be interested to see the code in main() for the C program.

I don't see && overloaded. Thus this line looks strange:

Quote:

if( ( INPUT_PORT && INPUT_PIN ) == INPUT_PIN )

as is
Quote:

( *port_ && ( 1 << bit_ ) ) == ( 1 << bit_ )

I can't see how the = overload with a conditional statement can ever be the "same" as the straight forward assignments

Quote:

LED_PORT |= LED_PIN; // Turns the led on
LED_PORT &= ~LED_PIN; // Turns it off

or using something like bit_clear()/bit_set() for eye candy.

[As an example the snippet is fine; most of us toggle port pins nowadays by writing to PINx.]

Explore further, but I can't buy in to your conclusion

Quote:

But the main point is that we have object oriented code, clean syntax and no extra overhead; that is quantitative.


just yet. The "clean" syntax is a point versus
Quote:

LED_PORT |= LED_PIN; // Turns the led on
LED_PORT &= ~LED_PIN; // Turns it off


but the equivalent eye candy can be added in C as well (just as you added the overload methods).

Maybe it isn't such a good example after all-- How do "true" and "false" know whether it is an active-high or active-low LED?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

While I haven't really got the first idea what's going on within that I'm guessing that you are changing the usually understood meaning of the ~ operator so that it just toggles one bit in a port:

  type& operator ~(){ 
    *port_ ^= ( 1 << bit_ );

then in main() you use:

    if( enabled ) ~led;      // Toggle it! 

which I take it is toggling bit 2 of PORTB?

Exactly how does this "trick" aid the maintainability of the code? Someone looking simply at main() who understands standard C/C++ operators will see:

    if( enabled ) ~led;

as a complete bit inversion of the entire variable with no apparent effect. In normal use just "NOT var" in itself would not actually DO anything (unless the result were being assigned to an lvalue)

So it may make for a "clever trick" but I don't see how it makes the code more readable or maintainable?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch I think this is one example where c++ would be good, though I still wonder about the overhead.
In the class constructor you could add a params to define the behavior of the pin such as active high or low. Lets assume that assigning true should always mean activating. If you ever had to change what state is active you would only have to change the param passed to the constructor to be active low instead, and the rest of your code would still work without change.

Not to say that you couldn't just do a search and replace in c.

Hope that made sense.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
I'd be interested to see the code in main() for the C program.

Oh, dear... perhaps I was ( for once in my life ) not quite loquacious enough in my last post. The first code block is the program, in its entirety in the C++ version, the second code block is the C version. So the question about the strangeness of non-overloaded && and so forth is mute; that is simply a standard C mask and compare to check a pin.

As for the toggle implementation, I used the read-modify-write for as an expedient so that I would only be passing a single address into the OutPin constructor. Normally I would use the write to PINx trick as well ( assuming of course a modern AVR ).

Further, concerning the use of ~led vs. led.toggle(), I really have no major argument either way. I agree that simply overloading the negation operator can be confusing when you are entirely used to operators having no side effects, still if you simply read it as 'negate LED', it does make sense. Again, we are getting into the territory of religion as opposed to technical correctness or, perhaps, suitability.

Quote:

I can't see how the = overload with a conditional statement can ever be the "same" as the straight forward assignments

I must again disagree. Remember, I am looking at semantically equivalent programs and the assumption is that ( at any time ) the programmer only wants to modify one bit... the bit that is represented by the object. As such the |= and &= are required as opposed to just plain =. That just leaves the conditional. That is handled because the compiler can tell that only one path will ever be taken ( if a constant is passed ), and so it removes the conditional. Once that happens you end up with the same code that you would in the equivalent C program and it is similarly optimized. On the other hand, you can also assign a boolean variable to the output and it handles that properly as well; C code would require the conditional to be added by the programmer.

And yes, I've been rather stingy about the eye candy in my C version. I don't know it as well, and I rarely use it in my own programming.

Martin Jay McKee

As with most things in engineering, the answer is an unabashed, "It depends."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In "the other thread"

Lee wrote:

-- I'm a bit surprised that those of you that are C++ literate didn't just post a snippet of how to invoke the class function from within the ISR.

-- ... And perhaps comment on the "most efficient" way to do it.

-- Will these efficient methods then have little baggage, and be suitable for a >>micro<<controller?

The past discussions have shown that if one uses common sense with C++ on the AVR microcontroller, C++ can be used without bloat. So if C++ is viable on AVRs then there must be a viable method to do work in ISRs, right?


As stated earlier, I will make a genuine effort to do some tests and demos this weekend. Lawnmower and dog comes first, though.

Somewhere in the vaults I have a link to a web-article about ISRs and C++. IIRC it is about the difficulties writing the ISR as such in C++. This might be desireable when eg encapsulating a timer - the overflow ISR being inside the capsulation rather than outside. Further, and still IIRC, the conclusion was that it is not doable (in gcc?). (Alas my aging memory is somewhat more volatile than desireable..)

So,
- if the ISR can not be inside the encapsulation it has to be outside, and thus
- the ISR needs to call a member function in the class.

We now have (at least) two options:
- The member function is static. Just cal it with

TheClass::TheMethod(...);

- The member function is not static (and not virtual). We now need the object. If the object is a static (sloppyly "global") variable, then just go ahead and

theObject::TheMethod(...);

Or if what we have is a pointer to the object, then of course do

theObjectPointer->TheMethod(...);

I will leave the details of the efficiency of those different calls to later, but principally:
- Calling a static member function should cost just the same as calling a C function. (The interesting test here is how well C++ behaves when we try to inline the function.)
- Calling a non-static member function will add an extra hidden parameter, the this-pointer. I will return on the details on how avr-gcc handles this, and what the cost is. In essence, the this-pinter is needed so that the member function knows which object instance it is dealing with. It is largely equivalent with passing a pointer to a struct with data into a C function, so in principle no extra cost here either. I'm hoping for an experiment which will show if the C++ comipler really performs as good as it should here.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

- Calling a static member function should cost just the same as calling a C function.

Based on prior discussions, let's say that is always true.

But, can one make skinny ISRs? In C, we don't call functions generally in ISRs because the register save/restore it creates precludes skinny ISRs. I might well have a quadrature encoder class, right? And at 4MHz my AVR can comfortably handle 80k edges/second with a pair of skinny ISRs. Non-skinny? No way.

So I guess the key is indeed

Quote:

The interesting test here is how well C++ behaves when we try to inline the function.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

So the question about the strangeness of non-overloaded && and so forth is mute; that is simply a standard C mask and compare to check a pin.

No, it isn't. && is not a mask.

Yes, I want to see the generated machine instructions. You claimed quantitative proof. But you don't seem interested in showing the underlying data. The code is garbage, as it is NOT a mask operation. And an if() cannot be as fast as a non-conditional.

You certainly can make me a believer after you post both generated code listings.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
PORTB += 7 to set bit 7 of port b high, PORTB -= 7 to set it low anyone?
Nope, never. "+" is addition and setting a bit can not be construed in any way as addition. "=" is assignment, but the statement does not assign 7 to anything. It is not at all clear to anyone reading the code that adding 7 to PORTB would set bit 7. Operator overloading can be great. Misuse of it can be horrendous.

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JohanEkdahl wrote:
Every time there seems to be a lack of substance to this claim...
This is about the accusation that C++ by principle, and "overall" produces inefficient code as compared to C.
Johan, these results are from two years ago. I posted file sizes for a serial -> LCD firmware written in C, C++ that is close to the C code as possible, and C++ that uses classes for AVR hardware devices and stores the data for those devices in a class object rather than as a global variable. Between the 3 versions, the sizes range over 2x for a given compiler. I published sizes for CodeVision, GCC, IAR, and ImageCraft.

Source code and assembly output is available for download and there is a summary description at http://www.avrcode.com/serial_lcd/.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you, Kevin! I'll pop over and have a look.

Reading this, makes me confused though:

Quote:
stores the data for those devices in a class object rather than as a global variable

If it could be done with one global variable in C, then it will not be needed to store it in an objert (as in an instance of a class) in C++. A static class member should do just fine.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JohanEkdahl wrote:
If it could be done with one global variable in C, then it will not be needed to store it in an objert (as in an instance of a class) in C++. A static class member should do just fine.
Good point. That's the difference between the two C++ versions. The C++ version that tried to be close to the C version used only static class storage for data or global #define's. However, the more objectified C++ used static class storage for some data that could be shared across instances of the same class, but used class instance storage for data that was unique to an instance of an object.

Thus, the big difference in expandability between the C and simple C++ programs versus the objectified C++ program is that latter could be easily expanded to multiple instances of a hardware device. So, if the program wanted to support two LCD displays, each LCD display class instance kept data specified to that hardware instance in its C++ instance.

For GCC, sizes of the C, simple C++, and objectified C++ were 720, 806, and 1574 bytes. For IAR, 626, 762, and 1112 bytes.

Compare the more direct C++ LCD code fragment

234 class Lcd {
 235 private
 236   // Turn on power to the display, no cursor
 237   static const unsigned char m_PWR_ON = 0x0C;
 238   // Clear display command
 239   static const unsigned char m_CLR_DSP = 0x01;
 240   // Set 8 data bits, 4 display lines
 241   static const unsigned char m_LINE_8x4 = 0x38;
 242   // Set 8 data bits
 243   static const unsigned char m_DATA_8 = 0x30;
 244 
 245 public:
 246   INLINE_FUNC_DECLARE(void init(void)) {
 247     // Set BAUD_J2:BAUD_J1 PULL-UPS active
 248     LCD_CONTROL_PORT = (1<<BAUD_J2) | (1<<BAUD_J1);
 249     // Set LCD_control BAUD_J2:BAUD_J1 to inputs
 250     LCD_CONTROL_DIR = 0xF2;
 251     
 252     Delay::millisec(15);

...

 455   gLcd.init();

to the C++ version that allows multiple LCD hardware objects

 212 class Lcd {
 213 private:
 214   // Turn on power to the display, no cursor
 215   static const unsigned char m_PWR_ON = 0x0C;
 216   // Clear display command
 217   static const unsigned char m_CLR_DSP = 0x01;
 218   // Set 8 data bits, 4 display lines
 219   static const unsigned char m_LINE_8x4 = 0x38;
 220   // Set 8 data bits
 221   static const unsigned char m_DATA_8 = 0x30;
 222 
 223   volatile unsigned char * m_ctrlPort;
 224   volatile unsigned char * m_ctrlDir;
 225   volatile unsigned char * m_dataPort;
 226   volatile unsigned char * m_dataDir;
 227   volatile unsigned char * m_dataPinReg;\
 228   unsigned char m_rsPin;
 229   unsigned char m_rwPin;
 230   unsigned char m_ePin;
 231 
 232   unsigned char busyWait(void);
 233 
 234 public:
 235   INLINE_FUNC_DECLARE(void init(volatile unsigned char* ctrlPort,
 236                                 volatile unsigned char* ctrlDir, 
 237                                 volatile unsigned char* dataPort,
 238                                 volatile unsigned char* dataDir,
 239                                 volatile unsigned char* dataPinReg,
 240                                 unsigned char rsPin,
 241                                 unsigned char rwPin,
 242                                 unsigned char ePin)) {
 243     m_ctrlPort = ctrlPort;
 244     m_ctrlDir = ctrlDir;
 245     m_dataPort = dataPort;
 246     m_dataDir = dataDir;
 247     m_dataPinReg = dataPinReg;
 248     m_rsPin = rsPin;
 249     m_rwPin = rwPin;
 250     m_ePin = ePin;
 251 
 252     *m_ctrlPort &= ~((1<<m_rsPin)|(1<<m_rwPin)|(1<<m_ePin)); // set low
 253     *m_ctrlDir |= ((1<<m_rsPin)|(1<<m_rwPin)|(1<<m_ePin)); // set as output
 254                    
 255     Delay::millisec(15);

...
 537   gLcd.init(&LCD_CONTROL_PORT, &LCD_CONTROL_DIR, &LCD_DATA_PORT,
 538             &LCD_DATA_DIR, &LCD_DATA_PIN_REG, LCD_RS, LCD_RW, LCD_E);

For GCC, the sizes of the C, simple C++, and objectified C++ versions were 720, 806, and 1574 bytes whereas IAR was 626, 762, and 1112.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For the current discussion we need to compare C apples with C++ apples, ignoring the C++ pears. :wink:

Did you do any research into where the higher cost of the simple C++ as compare to the C version came from? (No, I haven't popped over to your code yet, I'm struggling with a WinAVR/AVR Studio problem right now. Without that getting sorted there will be no playing around with C++..

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JohanEkdahl wrote:
For the current discussion we need to compare C apples with C++ apples, ignoring the C++ pears.
Good idea, but now I'm counting the minutes until lunch!
Quote:
Did you do any research into where the higher cost of the simple C++ as compare to the C version came from?
I did, but I don't recall the specifics.

The makefiles for the various source versions for various compilers are included in the project so that people can compare the assembly output.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What an interesting thread ...

I do quite a lot of work for a client who is based in one particular 'niche' market place.

Over the years we've done a number of projects ... typically they are the standard sort of thing ... some hardware ... some firmware ... some PC based configuration gunge ...

Over the last couple of weeks I've been doing some soul searching as to how we can control costs/time expenditure on these projects ...

The PC side of things has got totally out of hand and now takes 2/3 of all the resources for less than 10% of the value added to the projects -- we're looking into a move to C# on VS 2010 as a method of increasing encapsulation and code reuse to control that.

The firmware side of things doesn't give us much grief ... it's currently C based ... but a move to a more object orientated solution (C++?) could be an improvement here too.

I guess the real question that we are asking is, "What is the performance and memory hit that we would take in transferring to C++, assuming that we are not necessarily looking for the 'exotic' aspects or C++, but rather looking for the object reuse element?"

I'm not expecting an answer (or at least a direct answer) but I'm hoping for an informed debate as to the pros and cons of the C/C++ question as regards real life embedded type applications.

Mike.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
The firmware side of things doesn't give us much grief ... it's currently C based ... but a move to a more object orientated solution (C++?) could be an improvement here too.

Don't forget the ol' "if it works - don't fix it" principle. :D

I was hoping to have some contributions re the "informed debate", but right now I am not in possession of a working development environment. I have posted in earlier C++ threads on some aspects of the C v/s C++ performance in theory, but that is kind of academic. What I was hoping to do this evening and parts of sunday was to take a small but realistic app and implement it in corresponding C and C++ versions to get the fact on how the avr-gcc tool chain performs with C++. As it seems the $DEITIES do not want this to happen right now.

If you're genuinely interested of the subject, then there are a number of reads available: The book "Inside the C++ Object Model" by Stanley Lippman is still largely valid despite it's age. A Google for "C v/s C++", "C++ embedded" and the like gave some interesting hits last time I ran them. Over at www.embedded.com Dan Saks has a number of interesting articles on the subject (here's one: http://www.embedded.com/columns/... ). I have a binder smack full of stuff I collected over the last tw or three odd years, but I haven't had it open in six months. Let me know if I should browse through it for some further tips.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JohanEkdahl wrote:
What I was hoping to do this evening and parts of sunday was to take a small but realistic app and implement it in corresponding C and C++ versions to get the fact on how the avr-gcc tool chain performs with C++.
I hope that you get your development platform back in operation. Overall, I think the answer lies with "how much of C++features do you want to utilize?"

For the small; UART to LCD program with PWM backlighting control that I posted, the answer varies.

I did a quick "cp serial_lcd.c serial_lcd.cpp" and compiled with avr-gcc versus avr-g++. The answer is identical to the byte. OTOH, if porting to C++ and abstaining from all class variables then my project code has a 1.2x increase to C++, and up to 2x+ increase with class instance variables.

Based on the project that I posted, the answer ranges from a size increase of 1.0x size identical source code, to 1.2x for some C++ features without class instance objects, or 2x+ increase with class instance objects.

People can dissect the .S output to see exactly where assembly is increase for the C++ features used in this project. Also, there is significant compiler variation, up to 40% size difference between IAR and GCC for this small project.

Perhaps the ultimate answer to your question is "how much overhead is there for each type of C++ feature utilized versus an simple implementation in C" -- keeping in mind that the C++ features add significant source code advantages in terms of library development and code reuse. For the code I posted, the answer ranges from 1.0x for identical source code to 1.2x for some C++ features staying close to C version functionality. If you consider hand-tuned assembly for this small project, then maybe the multiple does down as low as 0.8x compared to the C version. The development and maintenance time clearly varies between the C, basic C++, and assembly versions.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

On C++ in 8 bit micros...

Someone please explain, for a non-trivial C++ program, the RAM usage implications of creating instances of objects, the timing implications of heap management and garbage collection in a system that's to be deterministic, etc.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

stevech wrote:
Someone please explain, for a non-trivial C++ program, the RAM usage implications of creating instances of objects, the timing implications of heap management and garbage collection in a system that's to be deterministic, etc.
It's simple enough to see for yourself the RAM usage (heap and stack depending upon where you allocate the object) if you look at the assembly output of the code that I posted. Also, the amount of program memory varies to access memory in the heap from a object instance versus accessing a static location with a plain C global variable.

As far as timing, it is likely to be near constant allocation time. There is no garbage collection in C++, only freeing of the instance when it goes out of scope as well as any memory freed by the object's destructor function.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

It's simple enough to see for yourself the RAM usage

No it's only simple for someone who knows how to write a C++ program. One of the questions above was about the overhead of calling a function from an ISR. If in C I do:

static void function(void) {
  PORTB ^= 0xFF;
}

ISR(TIMER1_OVF_vect) {
  function();
}

then (for a mega16) I get:

0000016e <__vector_8>:

ISR(TIMER1_OVF_vect) {
 16e:	1f 92       	push	r1
 170:	0f 92       	push	r0
 172:	0f b6       	in	r0, 0x3f	; 63
 174:	0f 92       	push	r0
 176:	11 24       	eor	r1, r1
 178:	8f 93       	push	r24

static void function(void) {
  PORTB ^= 0xFF;
 17a:	88 b3       	in	r24, 0x18	; 24
 17c:	80 95       	com	r24
 17e:	88 bb       	out	0x18, r24	; 24
}

 180:	8f 91       	pop	r24
 182:	0f 90       	pop	r0
 184:	0f be       	out	0x3f, r0	; 63
 186:	0f 90       	pop	r0
 188:	1f 90       	pop	r1
 18a:	18 95       	reti

I'd just like someone who can program C++ to do exact the same thing in C++ and verify that the code generated is the same. In the thread that started this one Steve suggested:

ISR(Whatever_vect) 
{ 
    MyClass::staticFunc(); 
} 

so how do I write "MyClass" and "staticFunc()" to be able to do EXACTLY that? And can we verify that in doing so the "overhead" is exactly the same as the plain C equivalent above?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
I'd just like someone who can program C++ to do exact the same thing in C++ and verify that the code generated is the same.
Good point, it only seems trivial to someone who knows C++.
But, for your example the C++ code would be
class A {
public:
  static void function(void) {
    PORTB ^= 0xFF;
  }
};

ISR(TIMER1_OVF_vect) {
  A::function();
} 

and the output is

ISR(TIMER1_OVF_vect) {                                                                                         
  34:   1f 92           push    r1
  36:   0f 92           push    r0
  38:   0f b6           in      r0, 0x3f        ; 63
  3a:   0f 92           push    r0
  3c:   11 24           eor     r1, r1
  3e:   8f 93           push    r24
#include 

class A {
public:
  static void function(void) {
    PORTB ^= 0xFF;
  40:   88 b3           in      r24, 0x18       ; 24
  42:   80 95           com     r24
  44:   88 bb           out     0x18, r24       ; 24
  }
};                                                                                                             

ISR(TIMER1_OVF_vect) {
  A::function();
}
  46:   8f 91           pop     r24
  48:   0f 90           pop     r0
  4a:   0f be           out     0x3f, r0        ; 63
  4c:   0f 90           pop     r0
  4e:   1f 90           pop     r1                                                                             
  50:   18 95           reti  

Which is exactly the same as the C result.

In the project I published, the C++ ISR to store an incoming UART character is

ISR(USART_RX_vect)
{
  gUart.storeChar(UDR);
}

The resulting assembly is 58 bytes versus the C code which is 62 bytes.

You asked how to know the C and C++ output will be the same. As you know, the compilers make no guarantees and output code has been seen to change over versions from the same compiler vendor.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

The resulting assembly is 58 bytes versus the C code which is 62 bytes.

Interesting - what's C doing in the other 1 / 2 opcodes?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Someone please explain, for a non-trivial C++ program, the RAM usage implications of creating instances of objects, the timing implications of heap management and garbage collection in a system that's to be deterministic, etc.
The implications of dynamically creating objects in C++ is for the most part about the same as using malloc in C programs.

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have just got AVR Studio going again (and started setting up the most rudimentary test). It's now 7.30 pm so there will be little more done today.

Thanks to kmr who has shown a call to a static member function has the same overhead as the corresponding C case.

As for the "exactly" stuff: Why would this "exactly" be interesting? When we discuss C v/s assembler we do not make these demands. We are willing to accept that the C compiler emits code that is slightly less efficient than the absolutely best written hand-optimized code produced by a seasoned AVR Assembler programmer (although we usually think that the so-so assembler programmer can only outperform the compiler when being lucky). Why the difference in "burden" here? Further, craving "exactly" the same code emitted is yhe absolutely easiest way for you to guarantee on beforehand that you will "win" the argument. You know that there will be some point where the C++ compiler will emit code differently from the C compiler. If that is your criteria we can stop here: You "win".

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

The implications of dynamically creating objects in C++ is for the most part about the same as using malloc in C programs.

And just to make it absolutely clear: The circumstances where creating objects dynamically come into play in C++ are the same as when malloc comes into play in C programs. In other words, if for a certain app you don't need malloc in your C app then you won't need dynamically created object in the corresponding C++ app.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

By "Exactly" I meant "exactly the same size/speed or less". I wasn't necessarily hoping for exactly the same opcodes.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Interesting - what's C doing in the other 1 / 2 opcodes?
I'll leave that as an exercise to the reader since a few opcodes is not what I consider essential to my overall findings. To aid, I have added GCC's assembly output to the git repository to those who are counting bytes. The repository is browsable at http://git.b9.com/?p=avr_serial_... and clonable at git://git.b9.com/avr_serial_lcd.git.

Rather than counting bytes, I'll stay with my take-home message that for my published case the overhead of C++ can be exactly zero for identical source code, to 1.2x (~100 bytes) for a small project that takes advantage of some C++ features to a factor of 2x that uses object encapsulation to allow multiple C++ object to encapsulate multiple hardware objects to exist in the same project. The more software overhead of processing versus the software overhead of multiple devices dictates how much the object encapsulation of C++ adds as a percentage to the overall code size.

In summary, the overhead of C++ depends upon how much of it that you use. Also, compiler differences (GCC vs IAR) can contribute as much as 40% difference in the size of the final C++ code.

As a new point, this firmware was targeted to a 2313. So, the difference of ~900 bytes difference between the GCC C++ version of ~1600 and the IAR C version of ~700 was a cost difference of zero in terms both software approaches fitting in a 2313.

Last Edited: Sun. Jun 6, 2010 - 06:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

By "Exactly" I meant "exactly the same size/speed or less".

OK, so you "win". I see no productive outcome of a comparison of C and C++ under those terms.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JohanEkdahl wrote:
Quote:

By "Exactly" I meant "exactly the same size/speed or less".

OK, so you "win". I see no productive outcome of a comparison of C and C++ under those terms.
I think that those comparisons can extended to C versus hand-tuned assember. I think it comes back to programmer productively using higher levels of abstraction versus the savings of using many less expensive MCUs where the smaller code size allows less expensive hardware system.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I wasn't trying to "win" I was trying to dispel the rumour that using C++ introduced some kind of hidden "bloat". Clearly, in this instance at least, it doesn't but what you C++ gurus need to do to convince the unbelievers is to post some more .lss examples. The naysayers can't write C++ so they cannot make comparative tests so it's up to you bilingualists to prove the point - there's always too much theoretical talk in these threads and not enough hard evidence presented.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK, Cliff. Sorry if you felt you got stung. I've seen your point about real examples, .lss and stuff. I'm working on it.

Still, I started this thread as there was another dropping of "C++ is bloat" w/o any proof whatsoever. Theoretically there should be little such bloat - none in many "standard design idioms". I have written about why this is so several times. We lack evidence on real world performance of AVR C++ compilers. For those genuinely interested there are some measurements done for other (GCC based) compilers available on the web, and they show no substantial overhead.

The bloat-criers, OTOH, has to my recollection not showed much of real world examples to prove their point. (There have been one or a few items where C++ with avr-gcc comes out less favourable - the one I can recall right now is that v-tables are in RAM.) And they have presented no theoretical reasons.

And, as I have been saying in previous threads: I am not after arguing for everyone to switch to C++. I am not arguing that C++ would be better in all, a majority, a minority or just a handful of cases. I am after the bloat-crying with no proof, which to me is is a perfect echo of an assembler fundamentalist crying "C is creating bloat". No, I'm not a day-to-day user of C++ on AVRs (yet :wink:) but still, when I see people crying bloat w/o a sinlge proof I must protest.

Proof could be in two different forms:

* Simple snippets, eg showing the cost of a call to a C++ member function, v/s calling a C function passing a pointer to a struct with data for the function to work on. This will show how identical demands on the software performs in the two languages, and it will be perfectly valid though I fear that if such proof is presented it will be met by "no, we need a real-world app".

* A demo application demonstrating the different "idioms" implemented. Both in C and C++. This is more work, but I have an idea of what to do.

I'd be very happy to get reactions on those before I commit myself to one of them. Would you trust that if an "idiom" is presented in a small demo that the compiler would generate principally the same code for similar cases?

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
I wasn't trying to "win" I was trying to dispel the rumour that using C++ introduced some kind of hidden "bloat".
A truly excellent goal, Cliff. Over the last year I've been working on massively concurrent systems spread across large number of servers (think cloud computing using a language like Erlang for large problems decomposable to parallel computation). It fun for me to think back to counting the bytes of code generated for each line of source code written.

While modern compilers have reduced this issue, I think it all comes back to learning how the source code that you write translates into object code. I consider C a high-level assembler not necessarily based on the level of abstraction of the source code, but because as I write source the code that I have a accurate estimate of what sort of assembly language will result. In that context, I've learned what C source code turns into more efficient object code. For example, I was surprised that C switches were so much less efficient that if ... else if ... else if forms. I expect that compiler technology will eventually remove that difference. But, for the moment I avoid switch() in C when I am trying to save space.

I think that experienced, embedded C writers will learn what sort of source code will produce what sort of object code. Similarly, multilingual writers will learn what sort of C++ forms have additional overhead and they can decided if the source-level abstractions outweigh any increase in the resultant object code.

Going back to a previous summary for Johan, I think a goal for your question may be to categorize what sorts of C++ abstractions have additional overhead compared using straight C-level code. This is fairly easily in the sense that the same C code is compiled equivalently between a C and C++ compiler. Keep in mind that your database of abstractions and their overhead may change with improved compiler technology, just the same way that compiler technology broke the long-held rule what you must use tail recursion in Erlang to have efficient recursive coding.

Going back to Cliff's statement, with experience there is no "hidden bloat", just the knowledge that some source code forms will generate larger code than other, less abstract source code forms.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JohanEkdahl wrote:
Would you trust that if an "idiom" is presented in a small demo that the compiler would generate principally the same code for similar cases?
I would, but how much trust would depend upon the efficiency of the compilers. I recall hearing stories (but not personally using), 24 pass Fortran compilers being used in clandestine aerospace environments. With more sophisticated compiler technology, trusting the results of a small case to generalize to a case embedded in a much larger system is less certain. Lastly, this is somewhat compiler specific with this case study show large differences in the C++ output across compilers as well as differences between compiler versions over time.

It does come down to the oft-repeated recommendation of "know your compiler".

In the end, that's why I worked on this case study, so that myself (and others) could analyze the results and make informed decisions about the cost of using specific language features.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

The bloat-criers, OTOH, has to my recollection not showed much of real world examples to prove their point.

I'll flip it around, and continue with Cliff's statement: The "other thread" had a claim of quantitative proof, and equal code sizes. But the implementation looked awfully fishy to be equal generated instruction sequences. And AFAIK no .LSS files were forthcoming. The Emperor wore no clothes.

kmr told us:

Quote:
For GCC, sizes of the C, simple C++, and objectified C++ were 720, 806, and 1574 bytes. For IAR, 626, 762, and 1112 bytes.

If we don't write non-trivial apps in "objectified C++", what have we gained over C? And if we do embrace object-oriented programming for non-trivial complete apps on AVRs, does it get 2x as big? Or not?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
If we don't write non-trivial apps in "objectified C++", what have we gained over C? And if we do embrace object-oriented programming for non-trivial complete apps on AVRs, does it get 2x as big? Or not?
I think there is middle ground. For something like the ATTiny2313 target, using the C++ code without instance objects still have advantages with only a small increase in size. Just separating the functionality into classes makes the code clearer.

On the other hand, for large projects, with multiple instances of objects (say a few UARTs and PWM outputs), then having each object store its own state should help with code reuse.

One reason I think that beginners like the Arduino, besides have a consistent hardware target, is that Arduino C++ library sets up a easy to understand map where hardware devices are associated with C++ objects. I don't spend a lot of time reading Arduino posts, but I haven't noticed anyone complain about the "bloat" of using the C++ based Wiring/Processing libraries.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

LordAdam wrote:

The PC side of things has got totally out of hand and now takes 2/3 of all the resources for less than 10% of the value added to the projects -- we're looking into a move to C# on VS 2010 as a method of increasing encapsulation and code reuse to control that.

Interesting. What is the PC code currently written in?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

On C++ and object/class instance create/destroy... On small RAM micros, using malloc() and free() is (IMO) really high risk. I do in some cases, where either
1) there will be no free() for that malloc(), and doing this helps avoid wasting RAM for structs or arrays whose existence or size isn't known at design time.
2) When virtually all of the malloc() and free() calls are for the same sized object, e.g., a task control block in an RTOS, and tasks come and go.

Some (most?) implementations of free(), on 8 bitters, don't try to conjoin blocks that are collocated in RAM but not in successive places in the free list. This can take too much time, on a little 8 bitter.

Isn't this the worry with C++?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I don't spend a lot of time reading Arduino posts, but I haven't noticed anyone complain about the "bloat" of using the C++ based Wiring/Processing libraries.

Oh then you haven't been reading widely enough. While Arduino is (IMAO) a most excellent "system" for getting folks started in MCU programming I've gone "in the backdoor" a number of times to look at the way things are actually implemented to help solve some users up front issues raised here and one things for sure they are not going to win any awards for "tight code solutions" !
Quote:

On small RAM micros, using malloc() and free() is (IMO) really high risk.

I agree with you about that and if an embedded program with 1K..2K of SRAM is doing multiple malloc()s/free()s then there's all sort of potential issues but I guess you are talking about how this malloc() usage maps to C++'s "new"? As I understand it in an MCU C++ program (but not desktop applications) then surely there's only going to be a few new's up at the top of the program where the timer or ADC or PWM or UART or whatever classes are first instantiated but it's not going to be dynamic is it? Surely the program creates these at the top then never frees them? So the worries about multiple malloc()s then free()s in C++ is unfounded isn't it? I guess it's a bit like locals created in main() of a plain C program - they effectively "live" for the entire life of the MCU (as long as main() never exits).

Maybe C++ gurus might like to comment on that? Maybe I'm mistaken and the paradigm is to only let things "live" for the time they are being used? IN which case maybe Steve has a point about heap fragmentation?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I always wondered if locals in main were really automatic stack variables or in bss.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I always wondered if locals in main were really automatic stack variables or in bss.

OT: Don't know about your C compiler but mine does:

0000708a 
: int main(void) __attribute__((OS_main)); int main(void) { 708a: cd b7 in r28, 0x3d ; 61 708c: de b7 in r29, 0x3e ; 62 708e: 27 97 sbiw r28, 0x07 ; 7 7090: de bf out 0x3e, r29 ; 62 7092: cd bf out 0x3d, r28 ; 61 int n; char c; long l;

(the 7 bytes for the int, char and long are created on the stack - but it may be different because this is a one stack C compiler)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Isn't this the worry with C++?

The C++ thing that corresponds with C's malloc() is the new operator. Just as you are not forced to use malloc in C, you are not forced to use the new operator in C++. Comparing a C app that does not malloc() with a C++ app that uses the new opertator is an apples-and-pears-situation.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Johan,

But isn't the point I made also true that on the whole an embedded C++ program very rarely actually invokes destructors (and therefore free()s ) because things are instantiated at power on then never destroyed (because the code never exits). Or am I wrong about that?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all, I just wanted to share some little experience I had with C++ in embedded devices.
I wrote some posts here http://cplusadd.blogspot.com/200... and here http://cplusadd.blogspot.com/200... about using pins as input/output/UART with the LPC2000 Philips family, but it is still GCC and it should be the same in the AVR world.

However, I believe that the problem is not what C++ brings in, but what it brings out! One of the most important drawbacks that made me stay away of C++ for embedded systems is the lack of designated initializers for structs, and this also takes me to the problem of trying to define const objects to lie in ROM (Flash) when these objects are from a class that has non-integral types (see the first link above).

I believe C++ is quite beautiful and could beautify and make many things simpler without or with a very little overhead (this can be tested and controlled). However, this const stuff is very important for embedded systems and made me stay away from C++ in AVRs and LPCs.

EDIT: btw, the examples posted in the links above generate the same code when compared to the pure C version (maybe some more initialization calls, I don't remember right now)

Regards,
Carlos

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

But isn't the point I made also true that on the whole an embedded C++ program very rarely actually invokes destructors (and therefore free()s ) because things are instantiated at power on then never destroyed (because the code never exits). Or am I wrong about that?

OK, I sense a communication problem here. Probably on my end...

Yes, for objects instantiated at startup (statically allocated objects) the destructor never runs, because we never get out of "the loop in main". But this is true for C variables also. They have eternal extent, so to say.

Now, let's deal with the "very rarely is destructors invoked". There is no magic here. A destructor is invoked when the variables extent ends (not sure if that is proper *Queens English, but I hope you get my drift..). So if I would create an object as an automatic variable, then its destructor would be called when the function exits.

The same stuff applies to C++ variables, including objects (instances of classes), as for any other variables:

An object defined af file scope as

MyClass myObject(constructorParameter);

is allocated in the same way as eg

int i;

in file scope. I call that "static" allocation (the word "static" now has more uses than is comfortable in this discussion - beware and don't jump to conclusions.
The constructor is run in the startup code and destructor for the objet is run in the "shutdown code" (i.e. never for an embedded app).

An object defined as local to a function

void foo()
{
   MyClass myLocalObject(constructorParameter);
}

is allocated just as any local variable would be

void bar()
{
   int i;
}

i.e. on the stack. I call this "automatic" allocation.
The constructor is run at allocation, and the destructor is run on function exit ("block exit" to be more precise).

Finally we have the dynamic allocation

MyClass * pObject = new MyClass(constructorParameter);

which is not much different than

int * pInt = malloc(sizeof(int));

The two are allocated in the same memory space, the heap. (No, the constructorParameter does not correspond to the sizeof(int) !)
The constructor is run on allocation, and the destructor is run when

delete pInt:

is executed (corresponding to doing free() on something malloc()'ed).

The first two cases above might come as a surprise to people fluent in other OO languages, mainly Java and C#.

We will leave passing object to functions to another post.. :wink:

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you, Carlos!

Yes, if I would speculate one thing we may end up with it to deem C++ to in parts fit badly with the Harvard architecture.

It has been noted earlier that v-tables are in RAM, but could be in flash, maybe with a small performance hit as the entries would have to be LPMed.

I have not looked into the possibilities to place objects as such in flash, but it is quite clear that even if PROGMEM and friends can be applied to classes, members and/or objects the LPM must come into play. This would be an akwardness much like ordinary C variables being 'const'ed does not put them in flash (for the avr-gcc compiler) and that they need to be LPMed before ordinary use.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Johan,

But isn't the point I made also true that on the whole an embedded C++ program very rarely actually invokes destructors (and therefore free()s ) because things are instantiated at power on then never destroyed (because the code never exits). Or am I wrong about that?

It is true that if you "new" an object at startup and only "delete" it at end (which, presumably would never happen in a micro), then there would be no worry. But Johan's point is that there is no need to use "new" to create your globals, just as there is no need to "malloc" a global integer in C. You just declare the object.

And whether or not the destructor is called is irrelevant, it is the "delete" being called that matters. If you have a local object that you create in a function, when that object goes out of scope, its destructor will need to be called. But how the memory is managed will be exactly the same as in C. If that object is "new"ed, then you will need "delete". If not, then that object will be on the stack, just as in C (with the destructor being called automatically).

Edit: I have to learn to type faster :)

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've been thinking about the demo app to and fro today. I was initially thinking I would write two parallell apps (one in C and one in C++), but am about to change my mind on this and start with writing the C app and then porting it to C++ step by step. For each step we should swee the performance hit.

I might even publish the C app before starting to port it so that you all have a chance to make sure I don't start with a C example rigged to have Real Shit Performance [tm].

Any thoughts, anyone?

I got one of the STK500 and some other stuff out last night, and started to rig the h/w platform. The app should run on an ATmega88 (or other model in the same "family"), and apart from that you'd need a 4x20 LCD display, eight buttons (as on the STK500) and maybe a buzzer. The app is a real-time clock app, so for a realistic run you'd need to run your AVR to run from a crystal or other "precise clock source" (sure, running from the internal RC @ 8 MHz will work, but the clock(s) will drift).

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JohanEkdahl wrote:
Any thoughts, anyone?
As far I understand your question is "how much overhead is there in using a particular C++ feature". You may wish to keep multiple versions as you go from C to more C++ features. The overhead of those features can change with compiler versions. Also, over time you may wish to modify the source over time as you optimize the output while keeping those same C++ features.

You should explicitly define what C++ features each version will use. For example, my project started with optimized C. Then I made a goal to rewrite without any instance data. Then, add instance data. The features you wish to add and examine the effects of will be different. Also, you're trying to have a "apples to apples" comparison the best that I can tell.

Also, you may want to use subdirectories for each source version. That way, you can just go into each directory, do a build, and examine the report files for that version.

As for your project being a real-time clock, that sounds fine. Of course, if your project is a serial LCD backpack with PWM control of backlighting, well you already have a good base to start with ;)

Pages