What Makes C++ "Hard"?

Go To Last Post
186 posts / 0 new

Pages

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

It's not so much the older AVRs, it's the older AVR programmers.  indecision

I still have a tube of 90S1200s.  Perhaps I should keep them as antiques.  S.

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

Scroungre wrote:
It also strikes me as a recipe for inefficiency, although I've never seen speed comparisons of half-decent C++ vs. C code on AVRs. 

Oh, come on! That's just FUD.

 

Give examples.

 

If this is about using things when not needed, or using things in ignorance of their costs or other drawbacks, then I could argue about C that it is a recipe for inefficiency (well, even disaster) because someone might use dynamic memory allocation on a RAM-challenged AVR. If misuse of the language by ignorant users is a valid argument then not even assembler goes free..

 

So, can you be specific about what inefficiencies you expect C++ to force upon you? Facts, not hunches.

 

Sorry for the tone, but insinuations that C++ is inefficient surfaces ever so often. Here and elsewhere. And it's always like 99% ignorance.

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

An 'Object' contains both data (attributes) and code (procedures) all in one lump.  An object-oriented program is then constructed out of many of these lumps.

 But ... those "lumps" are just compile-time abstractions.  Even on a desktop von Neuman machine, your code is likely to end up in nice execute/readonly pages, and your data not remotely nearby (probably dynamically allocated, in fact.)  You might as well claim a C can't be supported on a Harvard architecture, because you can't distinguish "pointers to code" from "pointers to data."  (not that that isn't a problem.  But it's not nearly as much of a problem as you might think at first glance.)

 

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

JohanEkdahl wrote:

Scroungre wrote:
It also strikes me as a recipe for inefficiency, although I've never seen speed comparisons of half-decent C++ vs. C code on AVRs. 

Oh, come on! That's just FUD.

 

Give examples.

 

I don't have any examples. 

I believe I said so.  See quotation above.

 

C++ could be slower.  C++ could be faster.  It's rare, though, that I see C++ advertised as high-performance.

 

And the whole point of the thread was "What makes C++ so "Hard"?"

This is why I think it's "hard" from my perspective.  This is my opinion.

 

And dynamic RAM allocation on a part that doesn't have any RAM will get you nowhere (some Tiny chips were like that.  I guess you could call the register file RAM if you wanted to).  I would hope that during compiling a program that tried to allocate more RAM than the chip had would cause errors, but I'm not sure.  If the compiler didn't error out the program certainly would.

 

I don't know enough C++ to write fair comparison tests.  I'm not going to compare C++ with hand-drawn assembler, because the latter is known to be a bit faster than bare C, and using the latter again as a comparison with C++ wouldn't be fair.  If anyone else would like to, that could be the test we need.

 

S.

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

And obviously both C and C++ can be supported on a Harvard Architecture, because they both are.  Whether they are supported efficiently or not is an entirely different question.  S.

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

westfw wrote:
your code is likely to end up in nice execute/readonly pages, and your data not remotely nearby (probably dynamically allocated, in fact.)
+20

 

That's exactly what happens. Try debugging C++ in the Asm view on a PC (I do - old habits die hard!) and you will see exactly where things are located. In a class the "members" are quite distant from the methods that act upon them. So the memory space split in AVR is really no different.

 

As for "efficiency". I build small C and C++ examples to help in threads here all the time and on the whole the "core" of most functionality turns out almost identical whether C or C++ so this often proffered myth of C++ being "inefficient"/"bloaty" is just that, a myth. The one exception to this are the virtual table pointers which are known at compile time and thus could be flash located and accessed with LPM but in reality are RAM based - so they "eat" a bit of precious RAM that was not really required. But that's about the only inefficiency in C++ and, if only the GNU C++ dev team would allow such tings, the operation of compiler could be made more efficient for Harvard architecture processors.

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

Speaking of lambdas (thank you for the separate thread!), did C++ get any similar in-place creation of static const arrays and structures?  It's one of the things I've seen newbies try to do, and I've occasionally wanted something similar myself.  It seems like it would fall somewhere between quoted strings and lambdas in compile-time complexity:

  

typedef struct { volatile uint8_t & Port; uint8_t val } config_t;
  :
  write_sfrs({{config_t[]}}{
      { UCRSR0A, INIT_SRA },
      { UCRSR0B, INIT_SRB },
      { UCRSR0C, INIT_SRC }
  });

  (where my made-up syntax "{{type[]}}{stuff}" is supposed to mean "build a static const array of values of that type, containing "stuff")

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

I accept clawson's verdict on this.  S.

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

Scroungre wrote:
It's rare, though, that I see C++ advertised as high-performance.

That might be so, but is at least in part because you aren't looking. C++ folks themselves state repeatedly that one of the primary design goals is for C++ to be efficient. Very efficient. Right from the start.

 

You'd think that by now, 30+ years later, someone would have revealed the scam.

 

So, how often do you see C++ advertised as low-performance. And if so, is it backed up by anything?

 

Now, what does your "recipe for inefficiency" really amount to? Nothing, so far. No facts on the table. Not one little hint as to when this should be the case. Just a hunch.

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]

Last Edited: Fri. Jul 14, 2017 - 10:19 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Scroungre wrote:
And obviously both C and C++ can be supported on a Harvard Architecture, because they both are.  Whether they are supported efficiently or not is an entirely different question. 

More FUD.

 

Come on, Scrounge. Put some evidence on the table!

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

Please see posts #107 and #109.  Thank you.  S.

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

How do we define 'efficient'? We seem to be happy with our automobiles being only 30% efficient and javascript on our browsers is considered adequate when it is really inefficient, so it comes down to what we are prepared to accept.

Last Edited: Fri. Jul 14, 2017 - 11:59 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That is a good question, and like most good questions the answer is "it depends".

 

Spending a million bucks on software development to save one buck in hardware cost is a false economy - Unless you're shipping a million-plus units.

Burning away most of the flash to save a few cycles might be efficient - Or it might not (See:  Lookup tables vs. trig calculations).

 

As is typical of all engineering, it's a tradeoff.  Many tradeoffs, and judging which is the correct one is what engineers are supposed to do.

 

From my standpoint, I believe in using the least silicon to do the job, and although my time is valuable, I'd rather spend more time to use less silicon.  Others will, perfectly reasonably, disagree.

 

I am also distinctly unhappy with javascript on my web browsers and go to some efforts to limit it.

 

S.

 

"Engineering is the art of doing for a shilling what anyone can do for a pound".  Attributed to Nevil Shute Norway.

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

Scroungre, you've never seen speed comparisons between C and C++ because you've never looked, there are tons of them.

 

General C/C++ ranting...

C++ speed is basically the same as C if written well.

C++ IS the language of choice in almost all applications requiring speed. It's a better C.

There ARE ways to make C++ slower, but thats because you write the code to be slow.

 

In 30 years of writing control software in C++ I've had to worry about speed like twice. Both doing heavy image analysis. Both situation were not solved by using assembler or dropping down to C, but by profiling the code and fixing the slow parts.

 

One of my favorite software conference classes was one given by the compiler writers from Borland, Microsoft and a few other big name compilers on how to optimize your code.

It started with them asking the audience about their favorite ways to write faster code. They wrote these down on the projector then ripped then to shreds showing why it was wrong.

Some audience members even trotted out the assembly language proof that these were faster and the writers, in every case, proved them wrong.

 

It was hilarious and basically turned into a rant about writing clean, standard obvious code so the optimizer can do it's analysis and create better decisions about what changes to make.

This does not of course mean you can do stupid things like putting code in loops that shouldn't be there.

 

So if you think you are smarter than the compiler writers, then go ahead, optimize away.

The language you use is a very small part of the speed issue.

Keith Vasilakes

Firmware engineer

Minnesota

Last Edited: Fri. Jul 14, 2017 - 03:45 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

keith v wrote:

So if you think you are smarter than the compiler writers, then go ahead, optimize away.

 

As a matter of fact, yes, I do.  I write native assembler.  And lo, it's faster.

I'm not smarter than the compiler writers, it's just that my code is faster, uses less space, and takes ten times longer to write.

 

Scroungre wrote:

Please see posts #107 and #109.  Thank you.  S.

 

Does anyone here actually read anything I write once they're all offended?

S.

 

 

PS - I write control software that does depend upon speed.  And I don't write it in C++.  Thank you.

Edited to add PS

 

Last Edited: Fri. Jul 14, 2017 - 03:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It's not clear enough I guess, but I was only referencing your comment that you haven't seen any speed comparisions between the languages.

The rest of the post is not targeted at you.

 

Unless youve written your application in all 3 languages and are proficient in all 3 and then profiled your code, your statement that your assembler is faster is pure fantasy.

I have written assembler and C versions of the same app and I can confirm the asm version took 10 times longer to write.

Keith Vasilakes

Firmware engineer

Minnesota

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

Indeed, I had not.  But clawson had.  I deferred to his superior experience.  Did you miss that bit?

 

I believe it is a matter of fact that hand-tuned assembler is faster than compiled assembler.  Provided both know what they are doing.  I like to think that I do.  I have no idea what the compiler authors think.

 

In many a post before I pointed out that I don't know enough C++ to write a reasonable comparison.  So I won't.  If you would like to, you're more than welcome.

 

Otherwise, I concur.

 

S.

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

Scroungre wrote:
I believe it is a matter of fact that hand-tuned assembler is faster than compiled assembler.
OK, try to improve this...

C:\SysGCC\avr\bin>type avr.c
#include <avr/io.h>

int main(void) {
        while (1) {
                PORTB |= (1 << 5);
        }
}

C:\SysGCC\avr\bin>avr-gcc -mmcu=atmega16 -Os -g avr.c -o avr.elf

C:\SysGCC\avr\bin>avr-objdump -S avr.elf | tail -n 15

0000006c <main>:
#include <avr/io.h>

int main(void) {
        while (1) {
                PORTB |= (1 << 5);
  6c:   c5 9a           sbi     0x18, 5 ; 24
  6e:   fe cf           rjmp    .-4             ; 0x6c <main>

C / C++ compilers don't ALWAYS make a bad job of picking Asm opcodes ;-)

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

Here you go:

 

Unfortunately the 'code' bit on the fora doesn't work with my web browser, and so I'm going to have to just cook it up:

 

C:\whatever\type avr.asm

 

LABEL:

sbi PORTB, 5

rjmp LABEL

 

And here everyone tells me that writing assembler code is verbose.  Sheesh.

 

smiley   S.

 

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

One way to beat the compiler is to know something it does not.

clawson wrote:

Scroungre wrote:
I believe it is a matter of fact that hand-tuned assembler is faster than compiled assembler.
OK, try to improve this...

C:\SysGCC\avr\bin>type avr.c
#include <avr/io.h>

int main(void) {
        while (1) {
                PORTB |= (1 << 5);
        }
}

C:\SysGCC\avr\bin>avr-gcc -mmcu=atmega16 -Os -g avr.c -o avr.elf

C:\SysGCC\avr\bin>avr-objdump -S avr.elf | tail -n 15

0000006c <main>:
#include <avr/io.h>

int main(void) {
        while (1) {
                PORTB |= (1 << 5);
  6c:   c5 9a           sbi     0x18, 5 ; 24
  6e:   fe cf           rjmp    .-4             ; 0x6c <main>

C / C++ compilers don't ALWAYS make a bad job of picking Asm opcodes ;-)

To make it faster, use -nostarfiles and whatever else it takes to put the sbi at 0.

After that, nothing really matters, since PORTB will never change.

If one is nevertheless interested in the loop rate,

the following are both faster:

0:  sbi PORTB, 5
    sbi PORTB, 5
    rjmp 0b

0:  ldi r16, 1<<5
2:  out PORTB, r16
    rjmp 2b

Dealing with the possibility of an always on watchdog is left as an exercise for the reader.

Edit: Nevermind. The default is reset mode.

Iluvatar is the better part of Valar.

Last Edited: Sat. Jul 15, 2017 - 12:25 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Lads I am talking about the two opcodes:

  6c:   c5 9a           sbi     0x18, 5 ; 24
  6e:   fe cf           rjmp    .-4             ; 0x6c <main>

How can what this does be made "shorter" in hand crafted asm? The assertion was "matter of fact ... assembler is faster than compiled assembler" (I take that to really mean "compiler generated assembler"?)

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

Okay, I'm ending this here.

 

clawson wrote:

C / C++ compilers don't ALWAYS make a bad job of picking Asm opcodes ;-)

 

Not always.  Good assembler coders don't ever.  So there.  Shall we be done with compiler fights now?

 

S.

 

Okay, if you really want to fight, show me C code that divides by 256 (and truncates) and I'll show you assembler that is faster.  S.

 

Last Edited: Fri. Jul 14, 2017 - 05:03 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Scroungre wrote:

Okay, if you really want to fight, show me C code that divides by 256 (and truncates) and I'll show you assembler that is faster.  S.

 

I'll bite. How do you make this faster?

c@foo:~$ cat divide-by-256.c
unsigned int div256(unsigned int x)
{
    return x / 256;
}
c@foo:~$ avr-gcc -O2 -S divide-by-256.c
c@foo:~$ cat divide-by-256.s
        .file   "divide-by-256.c"
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__tmp_reg__ = 0
__zero_reg__ = 1
        .text
.global div256
        .type   div256, @function
div256:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
        mov r24,r25
        ldi r25,0
        ret
        .size   div256, .-div256
        .ident  "GCC: (GNU) 4.9.2"

Seems that it's as small and fast as it can get (and it would be smaller/faster if it were inline and the compiler knew where the value were going, such as into an 8-bit variable).

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

The div256 routine is perfectly legal C, although that's what I'd call inline assembler.  The benefit of using pure assembler is that you don't have to move r25 into r24 and you don't have to clear r25 if you can just ignore it.  Save two cycles there.  And nothing about where the C function puts the return value.

 

S.

 

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

Just so that I understand you correctly.. Are you saying you call

unsigned int div256(unsigned int x)
{
    return x / 256;
}

"inline assembler"?

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

No.  I call:

 

christop wrote:

 

c@foo:~$ cat divide-by-256.c
div256:
        mov r24,r25
        ldi r25,0
        ret

 


 

inline assembler.  S.

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

(And I'd use 'clr r25' not because it makes any difference to execution speed but because it's shorter to type and makes is clearer (heh) what is intended to do with it.  Not that it matters - In assembler code the output can be any register, or register pair)  S.

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

clawson wrote:

Scroungre wrote:
I believe it is a matter of fact that hand-tuned assembler is faster than compiled assembler.
OK, try to improve this...

C:\SysGCC\avr\bin>type avr.c
#include <avr/io.h>

int main(void) {
        while (1) {
                PORTB |= (1 << 5);
        }
}

C:\SysGCC\avr\bin>avr-gcc -mmcu=atmega16 -Os -g avr.c -o avr.elf

C:\SysGCC\avr\bin>avr-objdump -S avr.elf | tail -n 15

0000006c <main>:
#include <avr/io.h>

int main(void) {
        while (1) {
                PORTB |= (1 << 5);
  6c:   c5 9a           sbi     0x18, 5 ; 24
  6e:   fe cf           rjmp    .-4             ; 0x6c <main>

C / C++ compilers don't ALWAYS make a bad job of picking Asm opcodes ;-)

 

Cmon, everyone knows that hand optimized ASM cannot be beaten, at most it can be matched, no need to start a war...

That one is easy, just unroll the loop.

 

loop:
    sbi     0x18, 5   
    sbi     0x18, 5   
    sbi     0x18, 5
    rjmp    loop

Now we have one I/O per 2.7 cycles, while the C code needs 4 cycles. Thus, 50% improvement in speed, at the cost of flash.

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

You might think so, Mr. Tangas, but it seems that some disagree with what both you and I find obvious.  S.

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

No.  I call:

[...]

inline assembler.  S.

 

OK. But that is the code generated by the compiler from the function in C written by christoph. 

 

Notice he is typing out the .lss file, which is a listing of what the compiler produced:

c@foo:~$ cat divide-by-256.s
   .
   .
   .

 

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]

Last Edited: Fri. Jul 14, 2017 - 09:34 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So, just to be clear...  If I have a C++ object like:

class SERCOM
{
	public:
		SERCOM(Sercom* s) ;

		/* ========== UART ========== */
		void initUART(SercomUartMode mode, SercomUartSampleRate sampleRate, uint32_t baudrate=0) ;
		void initFrame(SercomUartCharSize charSize, SercomDataOrder dataOrder, SercomParityMode parityMode, SercomNumberStopBit nbStopBits) ;
		void initPads(SercomUartTXPad txPad, SercomRXPad rxPad) ;

		void resetUART( void ) ;
	
		/* ========== SPI ========== */
		void initSPI(SercomSpiTXPad mosi, SercomRXPad miso, SercomSpiCharSize charSize, SercomDataOrder dataOrder) ;
		void initSPIClock(SercomSpiClockMode clockMode, uint32_t baudrate) ;

		void resetSPI( void ) ;
		void enableSPI( void ) ;
		void disableSPI( void ) ;

		/* ========== TWI ========== */
		void initSlaveWIRE(uint8_t address) ;
		void initMasterWIRE(uint32_t baudrate) ;

		void resetWIRE( void ) ;
		void enableWIRE( void ) ;
 // etc

 

All those per-class functions ("Methods") do NOT take up space in any SERCOM object I happen to declare or allocate.   Unless the methods are virtual, all the resolution of of "mysercom.resetWIRE();" and similar end up being just compile and link-time name-munging, and will end up in the object code as simple call instructions to absolute addresses.

(Is that right?)

 

If I DO declare the methods as virtual, then each object will end up with a pointer to a functions, and each call is equivalent to "(*struct->fptr)(args);", but with much simpler source-code syntax...

 

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

westfw wrote:
All those per-class functions ("Methods") do NOT take up space in any SERCOM object I happen to declare or allocate. 

If that is a question: No, they do not take up space.

 

I'll try this again (typed in explanations like this 10 times now I guess...), with a different angle:

 

There is nothing magic about objects. There  is nothing magic about member function calls. The objects holds the data from the class definition. It's like your good old C struct.

 

The call to a member function:

myObject.foo(int someParameter);

will be implemented as a call with parameters:

  1. The hidden this-pointer.
  2. Thee explicitly stated parameters (in this case one int).

 

It corresponds precisely to the C case

foo(MyStruct struct, int someParameter);

That's "all" there is to it.

 


 

For virtual funcdtions it get a little more hairy. Each object has a pointer to it's vtable. The vtable as such exists in one instance per class (not object!) with virtual methods. It is a table of pointers to the functions themselves.

 

Thus, calling a virtual function for an object, like so

class MyClass {

   virtual void bar(int i);
}

MyClass myObject;

int main() {
    myObject.bar(42);
}

means

  1. Follow the vtable pointer in the object.
  2. Pick the applicable function pointer from the vtable. NOTA BENE: This is a simple indexing operation! There is no "search" at run-time. The compiler has, at compile time, determined the offset into the vtable for the function in question.
  3. Push parameters (in this case the this-pointer and the 42.
  4. Call.

Points 3 and 4 the same as for any ordinary non-virtual call. So the extra cost is "two indirections".

 


 

If in doubt about what happens you can inspect the generated source and/or step through the disassembly.

 

Again, there's no magic here. And it's not very complicated. On a few occasions, when speaking to people about how C++ actually works and how it is not less efficient than C, the coin suddenly drops and they are surprised about the non-complexity of the "mechanisms behind the scenes". For anyone genuinely interested, I urge you to do the investigation into generated code. Things will be much clearer, e.g. re virtual functions.

 


 

And the "caveat emptor", as always when talking C++ on AVRs, efficiency, virtual functions and the avr-g++ compiler: Unfortunately, the compiler places the vtable in RAM. It could have been in flash ROM since it is immutable at run-time. The cost of a vtable is the size of a function pointer times the number of virtual functions the class has.

 

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]

Last Edited: Sat. Jul 22, 2017 - 09:54 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here's another view on it:

 

The first C++ compiler was "Cfront". This is what Bjarne et al used in the beginning. It compiles the first rudimentary C++ into C. Apart from making things independent of environment (OS, processor..) it made for inspecting what the compiler did easier. The generated C code was then compiled into a target-specific executable.

 

Simplifying/sketching things, here's what Cfront did to a simple class:

 

class C {
    int i;

    void f(j) {
        i = j;
    };
}

   .
   .
   .
   C object1;
   C object2;

   object1.f(42);
   object.f(7);

 

Compiled into C:

 

typedef struct {
    int i;

} C;

void f(C * this, int j) {
    this->i = j;
};

   .
   .
   .
   C object1;
   C object2;

   f(&object1, 42);
   f(&object2, 7);

Again: There's no magic. Just a bit of "behind the scenes".

 

Observe:

  • There is one implementation of f.
  • There is no extra cost of declaring a class, or rather defining an object, as compared to the C variant.

 

In reality it is a wee bit more convoluted, mainly because of overloading. There is the possibility to have several functions f() with different signatures, and thus "name mangling" comes into play.

 


 

It's a bit harder to make a simple readable example for virtual functions. I believe there will be a bit of typecasting going on because of the vtable holding function pointers to functions with different signatures. To be really sketchy:

class Super {
    int i;

    virtual void f(int j) {
        i = j + 1;
    }
}

class Sub : Super {
    virtual void f(int j) {
        i = j * 2;
    }
}

   .
   .
   .

   Super object1;
   Sub object2;
   Sub object3;

   object1.f(42);               // object1.i becomes 43

   object2.f(7);                // object2.i becomes 84

   object3.f(100);              // object3.i becomes 200

   Super * object4 = &object2;  // Up-cast is safe!
   object4->f(9);               // NOTA BENE: object2.i becomes 18!

The C++ code is really sketchy..

//--- The Super class ---

// Typedef for the vtable
typedef struct {
    void (*f) (int);
} SuperVtable;

// The vtable instance
SuperVtable superVtable = {
    .f = &Super_f;
}

The struct type for the Super class
typedef struct {
    int i;
    SuperVtable * vtable = superVtable;
} Super;

// The member function
void Super_f(Super * this, int j) {
    this->i = j + 1;
}

//--- The Sub class ---

// Typedef for the vtable
typedef struct {
    void (*f) (int);
} SubVtable;

// The vtable instance
SubVtable subVtable = {
    .f = &Sub_f;
}

// The struct type for the Sub class
typedef struct {
    SubVtable * vtable = subVtable;
} Sub;

// The member function
void Sub_f(int j) {
    i = j * 2;
}

   .
   .
   .

   Super object1;
   Sub object2;
   Sub object3;

   // object1.f(42);
   object1.vtable->f(42);               // object1.i becomes 43

   //object2.f(7);
   object2.vtable->f(7);                // object2.i becomes 14

   // object3.f(100);
   object3.vtable->f(100);              // object3.i becomes 200

   //Super * object4 = &object2;        // Up-cast is safe!
   // object4->f(9);
   object4->vtable->f(9);               // NOTA BENE: object2.i becomes 18!
                                        // While object3 is a pointer to a Super
                                        // it actually points to a Sub so the vtable
                                        // for a Sub will be used. THAT is polymorphism!

There's probably all sorts of formal shortcomings in the above C code. Forward declarations missing for one thing.

 

Please try to oversee that, and look at what is happening. 

 

hope I got everything correct as far as possible in the code above. If you see anything that seems to be a typo, please comment. 

 


 

If you really want to play around, it seems you can actually download Cfront from here: http://www.softwarepreservation.... . To be honest, I haven't ever done that but I've been tempted - just to see if I can get it going.

 


 

EDIT: I actually took a shot at pushing the above C code (second example, for virtual functions) through the compiler and it's a mess. See  below for something that I actually got to compile clean. (It does not add much to the illustration though - mostly extra fluff/confusion to keep the C compiler happy. See it only as a demonstration on what memory allovcation goes on, and how a virtual function call is implemented).

//--- The Super class ---

// Need a forward declaration of the vtable struct
typedef struct SuperVtable SuperVtable;

// The struct type for the Super class
typedef struct Super{
    int i;
    struct SuperVtable * vtable;
} Super;

typedef struct SuperVtable{
    void (*f) (struct Super * this, int j);
} SuperVtable;

// The member function
void Super_f(struct Super * this, int j) {
    this->i = j + 1;
}

// The vtable instance
SuperVtable superVtable = {
    .f = Super_f
};


#if 1
//--- The Sub class ---

// Need a forward declaration of the vtable struct
typedef struct SubVtable SubVtable;

// The struct type for the Sub class
typedef struct Sub {
    int i;
    SubVtable * vtable;
} Sub;

// Typedef for the vtable
typedef struct SubVtable{
    void (*f) (struct Sub * this, int j);
} SubVtable;

// The member function
void Sub_f(struct Sub * this, int j) {
    this->i = j * 2;
}
// The vtable instance
SubVtable subVtable = {
    .f = Sub_f
};



#endif

int main(char * argv[], int argc) {

   Super object1;
   object1.vtable = &superVtable;

#if 1
   Sub object2;
   object2.vtable = &subVtable;
   Sub object3;
   object3.vtable = &subVtable;
#endif
   
   // object1.f(42);
   superVtable.f(&object1, 42);         // object1.i becomes 43
                                        // I think, but not sure that...
                                        // since we have an object
                                        // variable rather than a pointer-to-object
                                        // the compiler can refer directly to the
                                        // vtable (without going through the
                                        // objects vtable-pointer). It KNOWS
                                        // object1 is precisely a Super.

   //object2.f(7);
   subVtable.f(&object2, 7);            // object2.i becomes 14

   // object3.f(100);
   object3.vtable->f(&object3, 100);    // object3.i becomes 200

   //Super * object4 = &object2;        // Up-cast is safe in C++!
                                        // In C Super and Sub are incompatible
                                        // types so it is not possible to
                                        // do
                                        //    Super * object4 = &object2;
                                        // Since I know that the layouts are
                                        // identical I will do an ugly cheat
                                        // and...
   Super * object4 = (Super *)(&object2);
   // object4->f(9);
   object4->vtable->f(object4, 9);      // NOTA BENE: object2.i becomes 18!
                                        // While object3 is a pointer to a Super
                                        // it actually points to a Sub so the vtable
                                        // for a Sub will be used. THAT is polymorphism!
}

Now I'm really getting tempted to try out Cfront! (But I have other things prioritized (apart from eternally defending C++ from FUD). Some of you might have seen my lengthy thread on getting a decent AVR development environment running on GNU/Linux (ICE debugging...). So Cfront will be pushed back.

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]

Last Edited: Sat. Jul 22, 2017 - 06:00 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A picture says...

 

 

NOTA BENE: Re hte placement of the vtables, this is specific to the avr-g++ compiler. Other compilers might behave better in this respect and place them in flash ROM.

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]

Last Edited: Sat. Jul 22, 2017 - 12:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In the IT industry it's Java, C# .NET and maybe PHP. Python and Ruby are new kids on the block. VB was the runt of the litter.

 

I understand the software industry loves C++. They tend to have advanced degrees though. Maybe that's why they don't get injured as much while handling a dangerous tool like C++.

 

In the IT industry it has a reputation for terrible applications the just keep breaking and extremely difficult bugs such as memory leaks, pointer issues, strange combinations of keywords that no one can figure out.

 

 

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

Darrough wrote:
In the IT industry it's Java, C# .NET and maybe PHP. Python and Ruby are new kids on the block. VB was the runt of the litter.

 

I understand the software industry loves C++. They tend to have advanced degrees though. Maybe that's why they don't get injured as much while handling a dangerous tool like C++.

 

In the IT industry it has a reputation for terrible applications the just keep breaking and extremely difficult bugs such as memory leaks, pointer issues, strange combinations of keywords that no one can figure out.

Anything can be done badly.

It seems that several someones have been writing code they do not understand.

That is one way to do it.

Iluvatar is the better part of Valar.

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

@Darrough:

 

You're not making much sense now, are you?

 

  • Earlier you have argued against C++ (and pro C it seemed) because of alleged inefficiency in C++. This claim has been rejected thoroughly.
  • Now you argue against C++ because of it being a "dangerous tool" and allows for "bugs such as memory leaks, pointer issues". Well, in that respect C is worse than C++, which e.g. does have a higher degree of type safety, and allows for somewhat safer pointer handling (if you know how to do it).

 

So on one hand you seem to argue for C over C++. On the other hand some arguments you pose re C++ holds in an even greater extent for C.

 


 

skeeve wrote:
In the IT industry it has a reputation for terrible applications

It is true that C++ is less "forgiving" than any of the high-level languages you name (Java, C#, Python, Ruby...) But since we are talking embedded systems here those language does not apply much for anything but the largest embedded systems. I.e. "hosted" systems (huge memory, has operating system etc) like a Busybox system, Android etc..

 

For systems "on the bare metal" those languages are not an alternative.

 


 

Darrough wrote:
strange combinations of keywords that no one can figure out

"No one" is a stupid and false generalization. It is true that one needs to learn the language to be able to use it. Suggest you stay away from 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]

Last Edited: Sat. Jul 22, 2017 - 06:15 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JohanEkdahl wrote:
Earlier you have argued against C++ (and pro C it seemed) because of alleged inefficiency in C++. This claim has been rejected thoroughly.

 

I did not argue against anything, merely asked if it wasn't true that object oriented programming and design patterns are not a good fit for small systems. You pointed out that some AVR projects can have 1000's of lines of code. You also made a point about reuse. I would not consider that "rejected thoroughly".

 

JohanEkdahl wrote:
Now you argue against C++ because of it being a "dangerous tool" and allows for "bugs such as memory leaks, pointer issues". Well, in that respect C is worse than C++, which e.g. does have a higher degree of type safety, and allows for somewhat safer pointer handling (if you know how to do it).

 

Once again, I did not argue against anything, I merely stated that it has a bad reputation in the IT industry. That is a fact which I can substantiate with 14 years of experience in IT.

 

From a technical point of view one cannot criticize C++, but there is more to the evaluation of a compiler than the technical facts. It has to be considered how well has it served programmers. And the largest body of programmers, the IT industry, pretty much won't have anything to do with it.

 

JohanEkdahl wrote:
"No one" is a stupid and false generalization. It is true that one needs to learn the language to be able to use it. Suggest you stay away from C++.

 

And I suggest that you take the blindfold off, because the languages numerous modifiers and specifiers are counter productive in a real programming environment.

 

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

The objects holds the data from the class definition.

 I do "get" this; I'm worried that C++ instruction contains so much "In an OOL, data and methods are combined into one object that groups them together" rhetoric that this becomes a worry of experienced embedded programmers.

 

 

the language's numerous modifiers and specifiers are counter productive in a real programming environment.

This is like saying "C's case sensitivity of symbol names is counter productive to understandable code, because you might use MyVariable and myvariable and my_variable and myVariable all in the same program."  If there are modifiers and specifiers that you feel are counter-productive in your environment, by all means feel free to NOT USE THEM.  And establish a coding standard that says that they shouldn't be used...  It's very rare for ANY computer language to have all of its features utilized...

 

The last place I worked had occasional discussions on whether we should start using or at least allowing C++ (already having a very OOP-in-C infrastructure.)  And our general conclusion was that it would make it too easy to "accidentally" do something with a significant performance impact, NOT that it had inherently poor performance.

 

As an interesting thought experiment, I doubt that there are many examples of "real" C++ programs" on the web that 10 randomly-chosen "experienced C++ programmers" will agree that "this is a good example of how a C++ program should be written."

 

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

I think you need a few years experience of both C and C++ before you can pass judgement on their relative merits. I have almost 20 years C and now 5+ for C++ and have now completely changed my initial opinion of C++. I'm now hard pushed to think of any way in which C is "better" than C++ and all this talk of slow/bloaty clearly comes from the ill informed who obviously aren't regularly using the language. 

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

clawson wrote:

I'm now hard pushed to think of any way in which C is "better" than C++ and all this talk of slow/bloaty clearly comes from the ill informed who obviously aren't regularly using the language. 


C has one feature that I like and use all the time that C++ doesn't have: designated initializers. I use them for creating all sorts of mapping arrays, such as a map of error codes to error message strings, and I also use them to create functions that can take named arguments. I'm posting this from my phone so I can't easily write code to show what I mean by that. I'll write an example later when I'm at a computer.

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

Chridtoph!

 

Not sure if this is helping you, but have you looked at C++11 constexpr?

 

http://en.cppreference.com/w/cpp...

http://www.sourcetricks.com/2013...

https://stackoverflow.com/questi...

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

Darrough wrote:
And I suggest that you take the blindfold off, because the languages numerous modifiers and specifiers are counter productive in a real programming environment.

 

In retrospect I think this comment was overstated. My apologies.

 

From a technical point of view there is nothing wrong with C or C++.

 

I just fanatical to give it to the programming community, watch it sink, and then declare that its not the compilers fault, the problem is with the programming community.

 

I am not criticizing the language. I am criticizing the fanatics that try to push C or C++ as the best all around language.

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

Surely the initializer list on the class c'tor serves the same purpose (initialization of selected members) it's simply a different syntax. 

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

OK, I'm at a computer now with some examples to show what I mean.

 

Here's my first example of designated initializers, for mapping arrays:

const char *error_messages[] = {
	[E_NONE] = "No error",
	[E_NO_SUCH_USER] = "No such user",
	[E_INVALID_INPUT] = "Invalid input",
	[E_TOO_MANY_TACOS] = "Too many tacos",
};

I also use that technique for mapping device-specific status codes to my own status code enumeration:

const enum Status device_status_map[] = {
	[0] = STATUS_EMPTY,
	[1] = STATUS_LOW,
	[2] = STATUS_OK,
	[3] = STATUS_ALMOST_FULL,
	[4] = STATUS_FULL,
};

I know there's maps and other such things in C++, but they are more heavyweight than using a small integer as an index into an array.

 

The other use, named arguments, borders on preprocessor abuse:

// our named argument structure
struct drawCircle {
	int x, y, radius;
};

// the real function
void drawCircleS(struct drawCircle dc) {
	// draw a circle using dc.x, dc.y, dc.radius
}

// our "function" that takes named arguments and has a default value for each argument.
#define drawCircle(...) drawCircleS((struct drawCircle){ .x = 1, .y = 2, .radius = 3, __VA_ARGS })

// then call it like this:
drawCircle(.x = 7, .y = 8, .radius = 9);

// or like this:
drawCircle(.radius = 9, .x = 7, .y = 8);

// or even like this to use the default radius:
drawCircle(.x = 7, .y = 8);

I know there's other ways to do something similar in C++, such as chained setters (e.g., parameters.setX(7).setY(8).setRadius(9).drawCircle()), but I like that it's compact and resembles other languages with named arguments, like Ada or Common Lisp.

 

JohanEkdahl: I'm not a C++ expert, so I'm not sure how constexpr can be used to implement something like the above. Do you have any examples?

 

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

OK, I see what you're after. Not even close to my hunch.. 

 

So constexpr is nothing that can do that for you. It's a mechanism to guarantee that a something is a build time expression (or emit an error if that's not possible).

 

I'm thinking your second case above might be implemented by setters, or maybe some named parameter idiom, hidden behind a VA_ARGS macro just as you do. Not enough knowledge about variadic macros, so would need to read up on stuff and then experiment.

 

I'm thinking even harder about your first example but can't see any such "neat" C++ solution.

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 am a little distressed that C and C++ seem to be diverging; I *liked* that C++ was supposed to be a superset of C.

Now we have Christop's designated initializers and the whole AVR "named address spaces" that aren't in C++   :-(

 

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

Yes. Some diverging is inevitable, methinks. The C++ folks had "almost a superset" as a design goal (never "a complete superset" as far as I know). But there was other design goals also.

 

If C "moves" (gets new features) then the C++ folks might well be in a situation where not following suite might break one design goal but following suite breaks another.

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

From a brief search it looks like Microsoft and clang have added extensions to C++ to allow designated initializer's and people are trying to get it added to the next C++ standard.

 

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

atomicdog wrote:
people are trying to get it added to the next C++ standard

Yes. And during a search I saw traces of attempts to have it added to C++14 C++17. That didn't happen.

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]

Last Edited: Mon. Jul 24, 2017 - 10:05 AM

Pages