views:

744

answers:

10

I'm programming in C for RAM limited embedded microcontroller with RTOS.

I regularly break my code to short functions, but every function calling require to more stack memory. Every task needs his stack, and this is one of the significant memory consumers in the project.

Is there an alternative to keep the code well organized and readable, still preserve the memory?

+4  A: 

Turn on optimization, specifically aggressive inlining. The compiler should be able to inline methods to minimize calls. Depending on the compiler and the optimization switches you use, marking some methods as inline may help (or it may be ignored).

With GCC, try adding the "-finline-functions" (or -O3) flag and possibly the " -finline-limit=n" flag.

Derek Park
+8  A: 

Try to make the call stack flatter, so instead of a() calling b() which calls c() which calls d(), have a() call b(), c(), and d() itself.

If a function is only referenced once, mark it inline (assuming your compiler supports this).

John Millikin
+1 inline....wondering if modern compilers do this automatically?
kenny
+6  A: 

In the event you can spare a lot of main memory but have only a small shred of stack, I suggest evaluating static allocations.

In C, all variables declared inside a function are "automatically managed" which means they're allocated on the stack.

Qualifying the declarations as "static" stores them in main memory instead of on the stack. They basically behave like global variables but still allow you to avoid the bad habits that come with overusing globals. You can make a good case for declaring large, long-lived buffers/variables as static to reduce pressure on the stack.

Beware that this doesn't work well/at all if your application is multithreaded or if you use recursion.

mbac32768
There's not usually a qualitative difference between RAM for stack and RAM for static allocation. You should be in control of the allocations via something such as a linker control file. Unless you have a complex processor with multiple RAM banks, such as on-chip RAM and separate external RAM.
Craig McQueen
A: 

Can you replace some of your local variables by globals? Arrays in particular can eat up stack.

If the situation allows you to share some globals between some those between functions, there is a chance you can reduce your memory foot print.

The trade off cost is increased complexity, and greater risk of unwanted side effects between functions vs a possibly smaller memory foot print.

What sort of variables do you have in your functions? What sizes and limits are we talking about?

EvilTeach
A: 

Depending on your compiler, and how aggressive your optimisation options are, you will have stack usage for every function call you make. So to start with you will probably need to limit the depth of your function calls. Some compilers do use jumps rather than branches for simple functions which will reduce stack usage. Obviously you can do the same thing by using, say, an assembler macro to jump to your functions rather than a direct function call.

As mentioned in other answers, inlining is one option available although that does come at the cost of greater code size.

The other area that eats stack is the local parameters. This area you do have some control over. Using (file level) statics will avoid stack allocation at the cost of your static ram allocation. Globals likewise.

In (truly) extreme cases you can come up with a convention for functions that uses a fixed number of global variables as temporary storage in lieu of locals on the stack. The tricky bit is making sure that none of the functions that use the same globals ever get called at the same time. (hence the convention)

Andrew Edgecombe
+8  A: 

There are 3 components to your stack usage:

  • Function Call return addresses
  • Function Call parameters
  • automatic(local) variables

The key to minimizing your stack usage is to minimize parameter passing and automatic variables. The space consumption of the actual function call itself is rather minimal.

Parameters

One way to address the parameter issue is to pass a structure (via pointer) instead of a large number of parameters.


foo(int a, int b, int c, int d)
{
...
   bar(int a, int b);
}

do this instead:


struct my_params {
   int a;
   int b;
   int c;
   int d;
};
foo(struct my_params* p)
{
   ...
   bar(p);
};

This strategy is good if you pass down a lot of parameters. If the parameters are all different, then it might not work well for you. You would end up with a large structure being passed around that contains many different parameters.

Automatic Variables (locals)

This tend to be the biggest consumer of stack space.

  • Arrays are the killer. Don't define arrays in your local functions!
  • Minimize the number of local variables.
  • Use the smallest type necessary.
  • If re-entrancy is not an issue, you can use module static variables.

Keep in mind that if you're simply moving all your local variables from local scope to module scope, you have NOT saved any space. You traded stack space for data segment space.

Some RTOS support thread local storage, which allocates "global" storage on a per-thread basis. This might allow you to have multiple independent global variables on a per task basis, but this will make your code not as straightforward.

Benoit
+1  A: 

One trick that I read somewhere inorder to evaluate the stack requirements of the code in an embedded setup is to fill the stack space at the onset with a known pattern(DEAD in hex being my favorite) and let the system run for a while.

After a normal run, read the stack space and see how much of the stack space has not been replaced during the course of operation. Design so as to leave atleast 150% of that so as to tackle all obsure code paths that might not have been exercised.

Vaibhav Garg
No, it isn't. My point being that you might not achieve 100% code coverage and may be missing a few code paths. Just a rule of thumb that I follow.
Vaibhav Garg
A: 

If you need to start preserving stack space you should either get a better compiler or more memory.

Your software will typically grow (new features,...) , so if you have to start a project by thinking about how to preserve stack space it's doomed from the beginning.

robert.berger
A: 

Yes, an RTOS can really eat up RAM for task stack usage. My experience is that as a new user of an RTOS, there's a tendency to use more tasks than necessary.

For an embedded system using an RTOS, RAM can be a precious commodity. To preserve RAM, for simple features it can still be effective to implement several features within one task, running in round-robin fashion, with a cooperative multitasking design. Thus reduce total number of tasks.

Craig McQueen
A: 

I think you may be imagining a problem which doesnt exist here. Most compilers don't actually do anything when they "allocate" automaticic variables on the stack.

The stack is allocated before "main()" is executed. When you call function b() from function a() the address of the storage area immediately after the last variable used by a is passed to b(). This becomes the start of b()'s stack if b() then calls function c() then c's stack starts after the last automatic variable defined by b().

Note that the stack memory is already there and allocated, that no initialisation takes place and the only processing involved is passing a stack pointer.

The only time this becomes a problem would be where all three functions use large amounts of storage the stack then has to accomadate the memory of all three functions. Try to keep functions which allocate large amounts of storage at the bottom of the call stack i.e. dont call another function from them.

Another trick for memory constained systems is to split of the memory hogging parts of a function into separate self contained functions.

James Anderson