g++ on Cortex-M with no dynamic memory

For years *one* of the *many* ways we've made reliable C++ embedded is by forbidding dynamic storage (except heap). Typically that requires:

- no exceptions or RTTI

- class new/delete/new[]/delete[] over-ridden with HCF

- all free-storage run-time pruned from the libraries

- minor compiler-specific stubs for un-used C++ stuff

A quick C++ trial showed that generated code didn't drag too much stuff in: only thing I see first pass is a pure base class vtable calls a "you-called-a-pure-virtual-function-you-idiot" function that dragged in free storage (easy to stub out).

For g++ on cortex-M, is there a standard way to do this? I didn't find anything at first Google... The toolchain I'm using is set up for nano.

Thanks in advance for any pointers! Best Regards, Dave

Reply to
Dave Nadler
Loading thread data ...

^^^^ You mean stack? Allocating memory at startup is reasonably safe: each time you allocate the same amount so can easily test if you stay within limit. Such allocation can be useful for static constructors in library functions.

AFAICS there are too many different approaches to talk about standard. You can use environment like Arduino, 'libmaple' or a C++ RTOS which sets up things for you. I used minimal setup:

- -fno-rtti -fno-exceptions flags to C++ compiler

- -fno-rtti -fno-exceptions -nostartfiles -nostdlib to the linker

and stubs for __cxa_pure_virtual and __aeabi_atexit:

void __cxa_pure_virtual(void) { while(1); ; }

int __aeabi_atexit (void *object, void (*destructor) (void *), void *dso_handle) { return 0; }

--
                              Waldek Hebisch
Reply to
antispam

Op 05-Nov-16 om 10:09 PM schreef Dave Nadler:

Nice to hear that I'm not the only user of that trick :)

Dunno about any standard way, you seem to use the same approach as I do.

Wouter "Objects? No Thanks!" van Ooijen

Reply to
Wouter van Ooijen

Except where the programmer then proceeds to create their own version of malloc/free on top of it :( Seen that, doh!

That's a (surmountable) problem for C/C++, since effectively the compiler flags become as important as the code itself: getting either wrong can cause application faults. Let's hope compiler version x.y.z hasn't changed the effect of each specific flag.

It is doubly unpleasant when you consider libraries supplied by a different company, possibly as a binary blob compiled with a different compiler to the one you are using.

Reply to
Tom Gardner

How?

The compiler flags make sure that the compiled code doesn't use RTTI & exceptions. Enabling either or both won't change the semantics of the code.

But generally yeah, when building your application for a small embedded system such low-level stuff is nearly unavoidably target and toolchain specific. But the bright side is that GCC is the only realistic toolchain that you can use over a range of such platforms. (Hey clang folks, when can I download a pre-built Clang cross system?)

Wouter "Objects? No Thanks" van Ooijen

Reply to
Wouter van Ooijen

And that is why compiler flags are given in your Makefile, and the Makefile also refers to the /exact/ version of your toolchain (compiler and library) used to build the code. And your Makefile is part of your source code, kept in your version control system along with the rest of the source code.

If you simply build your projects based on whatever version of "arm-none-eabi-gcc" happens to be first on your $PATH, or whatever happens to come with the latest version of the manufacturer's development tools, then you might have something that will work fine today - but you don't have something that will be fine in the future.

For the extra paranoid, you can add a sanity check file to your project, with code like this:

#if (__GNUC__ != 4) || (__GNUC_MINOR__ != 7) || (__GNUC_PATCHLEVEL__ != 2) #error This code is only tested for gcc 4.7.2 #endif

#if __EXCEPTIONS #error This code should be compiled with -fno-exceptions #endif

#if __GXX_RTTI #error This code should be compiled with -fno-rtti #endif

Reply to
David Brown

Sorry, stack only (no malloc/free/new/delete). And of course static ctors.

I was hoping for a standard way to:

- prune all memory allocation functions from libraries

- stub any RTL bits as you mention

Thanks, Best Regards,Dave

Reply to
Dave Nadler

The flags do more than that. There's definitely a difference in size, and what gets pulled in, when you have those turned on.

--
Tim Wescott 
Control systems, embedded software and circuit design 
 Click to see the full signature
Reply to
Tim Wescott

I don't know if there's a standard way, but there are various ways. It's been a while since I've done it, but search on topics like "porting newlib to a bace-metal platform" or some such. Newlib is the run-time library for no-OS or RTOS operation, and it leaves about a dozen functions un- functioned.

Personally, I allow for one-time object creation off of the heap, as long as it's all before the tasks start up and as long as nothing gets deleted and recreated. But that makes it hard to be strict about not allowing it.

IIRC, newlib calls the "OS base memory allocation" procedure -- I don't think you can get rid of it entirely, but you can stub this off to an assert. If you _do_ get rid of all allocation off of the heap, then just leaving it out of your collection -o- stubs will make the link fail when someone does something that calls 'new'.

--
Tim Wescott 
Control systems, embedded software and circuit design 
 Click to see the full signature
Reply to
Tim Wescott

That's exactly what I do:

// using malloc must cause a linker error void * malloc( size_t n ){ void if_you_see_this_you_are_using_new_or_malloc(); if_you_see_this_you_are_using_new_or_malloc(); return NULL; }

// using free must cause a linker error void free( void * p ){ void if_you_see_this_you_are_using_free_or_delete(); if_you_see_this_you_are_using_free_or_delete(); }

And btw I hate global ctor's (ctor ordering problem), so my linkerscript has:

.IF_YOU_SEE_THIS_YOU_HAVE_ONE_OR_MORE_GLOBAL_OBJECT_CONSTRUCTORS : { KEEP(*(.init)); KEEP(*(.preinit_array)); KEEP(*(SORT(.init_array.*))); KEEP(*(.init_array)); } > nul

Wouter "Objects? No Thanks!" van Ooijen

Reply to
Wouter van Ooijen

(.init_array.*)));

If you're not going to allow global constructors, what's the point of using C++?

As long as you're careful, and you can trust your initialization code to zero out BSS (which I can, because I wrote it, smirk smirk), then global constructor ordering issues are minor.

--
Tim Wescott 
Wescott Design Services 
 Click to see the full signature
Reply to
Tim Wescott

If I had to mention just one feature: templates. And no, my templates don't bloat the generated code, they reduce the code size.

And with constexpr the there is little need for global ctors. IMO global mutable objects are (genrally) a bad thing.

Wouter "Objects? No Thanks!" van Ooijen

Reply to
Wouter van Ooijen

Slightly off topic, why are C-programs so keen of using heap (malloc/free) instead of allocating temporary table variables (malloca) on stack as in e.g. Pascal ?

Even some FORTRAN IV compilers supported something like

SUBROUTINE FOO (N,M) DIMENSION TEMP (N,M)

In which the floating point two dimensional TEMP array on the subroutine stack had different size, depending on which parameters were used to call FOO.

Did this have something to do with the situation that some of the early processors used in C implementation did not support stack (or index register) relative addressing and only supported some kind of return address stack ?

Reply to
upsidedown

What is wrong with malloc(), I use it during the first millisecond of the program startup.

free() is a no-no, I disable it after the first millisecond and let the program run for 10-40 years.

Reply to
upsidedown

If that's the only time you use it, you can *probably* replace each instance with a suitable variable/array declaration (global)

So, all of your objects are persistent? (e.g., no "temporary/transient" tasks that come and go -- instantiating then releasing their stacks, etc.)

How would you, for example, build a set of cascaded menus? In artificially introduced function layers?

Reply to
Don Y

Op 07-Nov-16 om 10:01 PM schreef snipped-for-privacy@downunder.com:

If you need it, use it. I have an alternative version that I prefer not to use that supports malloc but not free. You can guess that its implementation is trivial.

But if possible, I prefer not to use malloc at all, because it proves beyond doubt that

- malloc isn't used after that initial phase

- apart from stack size(s), the application will fit in memory

This approach isn't suitable for all applications, but when it is suitable it a joy to use.

Wouter "Objects? No Thanks!" van Ooijen

Reply to
Wouter van Ooijen

_sbrk is the function I was thinking of. I have a functioning one for the reasons given above. But, if you're going to do your own board- support package for newlib, there's about a dozen others that you need to address.

--
Tim Wescott 
Control systems, embedded software and circuit design 
 Click to see the full signature
Reply to
Tim Wescott

And recompile the source code for each application and each customer with different parameters. No thanks.

The nice thing about stacks is that the worst case stack allocation can be determined statically. Even if recursion is used, the worst case allocation as long as you have a strict limit to the recursion depth.

The problem with heaps is the fragmentation of the heap in a long running system. It might seem running OK for the first year, but will it run for the next ten years ? One can not test a system for 10 years and then release it. With properly designed systems, running it for a few months should be enough. If it is going to fail in the far future, it will fail during the early test period (infant mortality).

What prevents using a stack based allocation system ?

Reply to
upsidedown

Exactly...

Reply to
Dave Nadler

Because of ordering issues, I've typically placed "global lifetime" objects as static objects inside (ordered by code) subsystem initialization routines. Then referred to them via static pointers. Not ideal but workable way to order the initialization.

Global ctors aside, C++ provides huge advantages:

- type safety

- RAII especially preventing resource leaks in dtors

- templates

- specialized storage allocation by class if required and so forth. All without any dynamic allocation except on stack.

If only there was a way to have exceptions without heap; exceptions really do help make safer code. Might be possible in some C++ toolchains if throws are limited to pointers (to static exception info)? Depends I guess on the implementation (ie does exception processing rely on RTTI).

Reply to
Dave Nadler

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