Go Down

Topic: Function calls and memory usage (Read 1 time) previous topic - next topic

thehobojoe

I recently made a thread about this issue, assuming that it was caused by a memory leak relating to string usage, however after much testing and learning I've found that the problem is instead much more plain: function calls.

I have a fairly large program that uses a lot of void functions, calling between each other constantly, most of the time not returning anything - at first I thought this was fine, but now I've learned that every time a function is called, it allocates memory in the stack that is not freed up until the call is returned. The problem is, on void functions, there's nothing to return and that would make the flow of the program an impossibility. The problem is that every time a function is called without being returned (Only a few of the functions in the program need to return) it uses up a few bytes of RAM. Sadly, this adds up really quick and after a enough function calls are made the Arduino runs out of memory and proceeds to bug out in a variety of annoying ways.

So my question is: Is there any way to free up the stack space from all the function calls that never get returned? Restructuring the program at this point is pretty much out of the question, it's too complex and intertwined and I'm not sure if the program could be fundamentally restructured without separation into functions as it is now. Or is there any way to 'trick' Arduino into dumping the memory without actually doing a return?

Why do voids work like this? You would expect a function that doesn't return anything to work fundamentally under that assumption - namely that it shouldn't hold RAM space that it will never use forever.

Hope someone can help, thanks in advance.

retrolefty

Quote
Why do voids work like this? You would expect a function that doesn't return anything to work fundamentally under that assumption - namely that it shouldn't hold RAM space that it will never use forever.


I'm not sure that is the case, but lets wait for the software gurus to comment.

Lefty


wildbill

Once you exit a function, whether explicitly from a return statement or simply by falling out of the bottom, all memory the function used on the stack is available for another function to use it. There is no difference in this behaviour between functions that return a value and those that don't (voids). There are a number of other scenarios that could cause the symptoms you see, post your code.

thehobojoe

Thanks for the info wildbill, but this goes against all the other places I've read and also my own testing - are you sure the memory is made available again once another function is called from inside a function?

Just to be clear, none of my functions fall out the bottom - all of them end either with return statements or another function call.

Function A will call function B and function B will call function F and function F will call function E... etc. And using the memory test available from here I can see memory being used only when calling a function that returns nothing - calling functions with returns doesn't hold memory the way that functions without returns do. I'll post some code later if possible.

PaulS

Stack space is reserved when a function is called, to hold the return argument AND all the data passed to the function. That space is not freed until the function ends.

Quote
none of my functions fall out the bottom - all of them end either with return statements or another function call.

If a function calls another function, it has not ended. It's data remains on the stack until a return (explicit or implicit) returns.

Quote
I'll post some code later if possible.

I think you need to.

wildbill

Certainly if you are calling a chain of functions as you describe, you will consume stack space. If function A calls function B that calls function C, then while function C is being executed, you have three frames on the stack, as your memory check will indicate. When C returns to B, the stack pointer will move back so that only the frames for A & B remain and a memory check will show you that. Similarly, the A frame will be in use until control returns from B and A finishes its work.

What you describe is very bizarre and would be catastrophic if true. There is something else causing your issue.

Msquare


all of them end either with return statements or another function call.
That is where you go wrong. That means the function has not exited at all. Either you have a major misunderstanding of functions or I have misread your explanation. But you wrote this twice explicitly and a couple of times indirectly.

There is something called co-routines but I do not think that can be setup in C - function calls are strictly nested.

Give us the source (or an reduced set of it) so we can comment/confirm/correct. (If it is too large, attach it as a file)

thehobojoe

Here is a portion that exhibits the problem:


Code: [Select]

void startmenu() {
menusize = 3;
GLCD.DrawRoundRect(0, 0, 126, 20, 5);
ptitle(28, 4, ("Main Menu"));
pnorm(3, 4, ("Start Run"));
pnorm(3, 5, ("Run Options"));
pnorm(3, 6, ("System Options"));
pnorm(3, 3, ("Pressure: "));
ListMenu.print(analogRead(6));
pnorm(0, cursor+3, ("->"));

while (1) {
buttons();
if (down == 1) {
scrolldown();
GLCD.FillRect(0,22,10,50,WHITE);
pnorm(0, cursor+3, ("->"));
ListMenu.CursorTo(0,7); ListMenu.print(freeMemory());}
if (up == 1) {
scrollup();
GLCD.FillRect(0,22,10,50,WHITE);
pnorm(0, cursor+3, ("->"));
ListMenu.CursorTo(0,7); ListMenu.print(freeMemory());}
if (select == 1) {
GLCD.ClearScreen();
switch (cursor) {
case 1: startrun();
case 2:
cursor = 1;
menupos = 1;
optionsmenu();}}
if (bitRead(pinvalues, butdown) == 1 && bitRead(pinvalues, butup) == 1) {
cursor = 1;
menupos = 1;
engmenu();}
}
}



void optionsmenu() {
menusize = 5;
GLCD.DrawRoundRect(0, 0, 126, 20, 5);
ptitle(40,4, ("Options"));
pnorm(3,3, (readoptions(menupos-1)));
pnorm(3,4, (readoptions(menupos)));
pnorm(3,5, (readoptions(menupos+1)));
pnorm(3,6, (readoptions(menupos+2)));
pnorm(3,7, (readoptions(menupos+3)));
pnorm(0,cursor-menupos+3, "->");
while (1) {
buttons();
if (down == 1){
scrolldown();
GLCD.FillRect(0,22,10,63,WHITE);
pnorm(0,cursor-menupos+3, "->");
ListMenu.CursorTo(0,7); ListMenu.print(freeMemory());}
if (up == 1){
scrollup();
GLCD.FillRect(0,22,10,63,WHITE);
pnorm(0,cursor-menupos+3, "->");
ListMenu.CursorTo(0,7); ListMenu.print(freeMemory());}
if (select == 1) {
switch(cursor) {
case 1: GLCD.ClearScreen(); extract();
case 2: foampurge();
case 3: decon();
case 4:
pnorm(0,cursor-menupos+3, "  ");
cursorlast = cursor;
menuposlast = menupos;
cursor = extsize;
extractvolume();
case 5:
GLCD.ClearScreen();
cursor = 2;
startmenu();
}
}
}
}


It's part of an LCD interface, it's fairly straightfoward. Each function declares the size of the menu(for scrolling etc), prints all the necessary lines, then checks for button presses. If I go back and forth between startmenu() and optionsmenu() (or to any other of the several similar functions the program has), it uses up a few bytes of RAM. If I understand what you are saying correctly, there is no way to free this memory? Apparently I still have a lot to learn then.

PaulS

You need to make sure that you are not performing recursive calls with your code. That is you should not have function a call function b which calls function a, or have function a call function a, unless you have a clear reason for doing so, and a way to limit recursion.

Quote
If I go back and forth between startmenu() and optionsmenu() (or to any other of the several similar functions the program has), it uses up a few bytes of RAM. If I understand what you are saying correctly, there is no way to free this memory?

There isn't because that memory isn't wasted/leaked (unless there is a problem with your code). It is still in use.

wildbill

You do indeed have the situation PaulS describes. In some circumstances, startmenu calls optionsmenu and it returns the favour. That will allow you to consume all the stack with ease, as you observe.

thehobojoe

Looks like I have a lot of restructuring work ahead of me then... Thanks for the help.

thehobojoe

Actually... Would it be possible to get around this issue using memory allocation?

wildbill

Not sure how that would help you, I think you could replace the call to startmenu with return and add a break after the call to optionmenu. Then, you just need to figure out how to invoke startmenu again, or tweak it with another while(1) that includes printing the menu on the LCD;

PaulS

Quote
Actually... Would it be possible to get around this issue using memory allocation?

Explain how you think that using more memory would help. Then, forget it.

When you go down a level in a menu, eventually you need to go back up, so that all the functions return and the stack gets shrunk.

Nick Gammon

I concur with the others here. And the fix is quite simple:

Code: [Select]
void optionsmenu() {
  ...
 
      case 5:
        GLCD.ClearScreen();
        cursor = 2;
        startmenu();
...

}


This is where you should return. You have a start menu which might show some options, right? When you "exit" the options menu you should not call another start menu because this is where the memory is being chewed up. You are done, so just return. eg.


Code: [Select]
void optionsmenu() {
  ...
 
      case 5:
        GLCD.ClearScreen();
        cursor = 2;
        return;
...

}



Quote
Is there any way to free up the stack space from all the function calls that never get returned?


No, because the stack is allocated for a reason. To hold the local variables (and the return address). You can't just say "I don't want my local variables any more". Returning from the function is the only way to free them up, whether by explicitly returning (using "return") or falling off the end of the function.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

Go Up