[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [avr-gcc-list] Re: Device fuses

From: dimax un
Subject: Re: [avr-gcc-list] Re: Device fuses
Date: Fri, 28 Apr 2006 09:31:54 +0300

WOW. I will definitely study your message. It will take time to all of us.
But my scepticism was not about templates but about writing embedded code in C++ instead of C/Assembler/InlineAssembler. Interesting what would you get if you would write one more example in plain good C.

Micro Connection to the Macro Vision
On 4/28/06, Ned Konz <address@hidden> wrote:

On Apr 25, 2006, at 10:48 AM, dimax un wrote:

> Never heard that C++ makes small code especially templates and
> especially in embedded systems.

Actually, templates are the best hope for small code using C++.

The secret is to have the right kind of templates, and to use the
compiler to do as much work as possible at compile time, rather than
making code on the embedded system do the work at runtime.

Templates have gotten a bad reputation for several reasons.

- some of the early implementations were less than optimal in their
- the standard took a while to solidify, after some experience with
the STL stuff
- the Embedded C++ people shunned them for their own reasons, and
then produced a number of documents blaming templates (and other
features that weren't present in C++ as of 20 years ago) for their
problems with C++.
- template instantiation was badly done in some compilers, leading to
- if you aren't careful about how you write templates you can end up
with code duplication where it's not needed (for instance,
instantiations for signed and unsigned flavors of template parameters
that are only used as indexes in arrays (and so would only be
positive in typical usage).

But there's nothing inherent in templates themselves that should
produce bigger code than equivalent C code.

Now, I'm not using the Standard C++ library as yet; I'm just using my
own templates for the time being.

Where can template help C++?

Let's take polymorphism. There are two kinds of polymorphic behavior
available in C++; one (the runtime flavor) has to do with using
pointers to objects that have compatible interfaces (in C++, of
course, compatibility is determined by inheritance).

The two kinds are orthogonal, of course; you often need to have both

Runtime polymorphism requires building a lookup table (the "vtable")
in memory somewhere (typically in RAM) that holds pointers to the
various virtual member functions; the object itself (also typically
in RAM) then points to that table.

If you really need runtime polymorphism, that's an efficient way to
do things.

But it's silly to pay that price when you know (or can compute) at
compile time the actual class of the receiver (the object on which
you're calling the member function). And this is a very common idiom
with smaller processors: we know up front what we're using USART1
for, and what we're using USART2 for. Likewise in the cases where we
have multiple units of similar hardware in our systems (CAN message
objects, timers, ADC channels, etc.).

For instance, I've seen code on the net that uses C++ for hobby
robotics education (using the AVR). It's cleanly written code that
would have been legal C++ 20 years ago (as I recall; I don't remember
precisely after all these years what the changes were between Cfront
1.1 and Cfront 1.2). No templates, namespaces, use of #define instead
of const, etc.

You can get the code here:

An example from this library (representing an input bit):

class IN
       /* constructor for setting the register ('A', 'B', etc)
          and pin (0-7), and specifying whether to enable the pull-up
          resistor [pull-up is on by default if not specified] */
       IN(char reg, char pin, bool fPullup = true);

       /* returns true if pin is reading high, else false */
       bool IsHigh() const;

       /* returns true if pin is reading low, else false */
       inline bool IsLow() const { return !IsHigh(); };

       /* helpers for switches */
       inline bool IsOpen() const   { return IsHigh(); }
       inline bool IsClosed() const { return IsLow();  }

       /* checks for button press; if not pressed, returns false; else
          waits for release (including debouncing both press & release) */
       bool WasPressed() const;

       unsigned char m_bit;
       volatile uint8_t *m_preg;

IN::IN(char reg, char pin, bool fPullup)
       volatile uint8_t *preg; // pointer to PORT register (for pull-up R)

       Assert(pin >= 0);
       Assert(pin <= 7);

       m_bit = 0x01 << pin;

       /* set direction for input; set m_preg, preg */
       switch (reg)
               m_bit = 0;      // error-handling
               // fall through so m_preg gets set...
       case 'a':
       case 'A':
               DDRA &= ~m_bit;
               m_preg = &PINA;
               preg = &PORTA;
       case 'b':
       case 'B':
               DDRB &= ~m_bit;
               m_preg = &PINB;
               preg = &PORTB;
       case 'c':
       case 'C':
               DDRC &= ~m_bit;
               m_preg = &PINC;
               preg = &PORTC;
       case 'd':
       case 'D':
               DDRD &= ~m_bit;
               m_preg = &PIND;
               preg = &PORTD;

       if (fPullup)
               *preg |= m_bit;         // enable pull-up resistor
               *preg &= ~m_bit;        // disable pull-up resistor

/* returns true if pin is reading high, else false */
bool IN::IsHigh() const
       return *m_preg & m_bit;

/* checks for button press; if not pressed, returns false; else
   waits for release (including debouncing both press & release) */
bool IN::WasPressed() const
       extern TIMER timer;

       if (IsOpen())
               return false;

       while (IsClosed())
       return true;


So this code takes three extra bytes of RAM (in addition to the three
hardware registers DDRx, PORTx, and PINx) per input port. And every
access requires looking at all of those bytes (dereferencing a
pointer, masking with a value from RAM).

But why? There's no way to change m_preg or m_bit once you've
constructed this object, so there's no real point in keeping those
things in RAM. Especially when you look at how these are used: all of
the IN objects are globals, and there's no passing of pointers to
these things during runtime (after construction; some global
constructors get passed IN or OUT object pointers). And there are no
virtual functions here (though of course templates work well with
virtual functions) so we don't need vtables. And I'm pretty sure
that, even with duplicated code snippets from template expansion, the
total ROM consumption would be less, as well.

Likewise, it's trivial to do compile-time asserts using templates for
template parameters (like 0 <= pin <= 7 above). No reason for a run-
time check when those parameters are compile-time constants!

The attached .cpp file shows the two styles; the OLD_STYLE is the
above (trimmed down to just the I/O part) and the templates are a
quick demo of the same.

You can compile it both ways (depending on the definition of the
OLD_STYLE macro) and compare the resultant code.

Note that:

* the OLD_STYLE includes functions that aren't called, because they
were in the source file. This forces library writers to break up
their source files to one function per file, more or less.

* using templates, only the functions that are called are instantiated.

* the template code is much smaller:

   text    data     bss     dec     hex filename
   1188       0      12    1200     4b0 test-old.elf (1985-style C++)
    320       0       4     324     144 test-new.elf (with templates)

* The compiler seems to reserve 1 byte of RAM for each object anyway
if you have a constructor for the class, even if you don't use it for
anything (see the 4 bytes in the .bss section for test-new above).
However, without constructors, the templates for I/O registers don't
take any RAM at all.

Try it for yourself!
Ned Konz
MetaMagix embedded consulting

[1] As I see it, Embedded C++ was a fear-motivated standard. Two
major fears are apparent to me:
- the fear (shared by the managers of development organizations in
big companies) of what the average- and below-average programmers
might do (and of the training budget required to keep them from doing
- the fear by some of the compiler vendors that they might actually
have to spend money updating their compilers before they were ready
to do so

reply via email to

[Prev in Thread] Current Thread [Next in Thread]