The main() function in an avr-gcc program saves the register state on the stack, but when the runtime calls it I understand on a microcontroller there isn't anything to return to. Is this a waste of RAM? How can this state saving be prevented?
It's all about the C-standard.
Nothing forbids you from exiting main at some time. You may not do it in your program, but others may do it.
Furthermore you can register cleanup-handlers via the atexit
runtime function. These functions need a defined register state to execute properly, and the only way to guarantee this is to save and restore the registers around main.
It could even be useful to do this: I don't know about the AVR but other micro-controllers can go into a low power state when they're done with their job and waiting for a reset. Doing this from a cleanup-handler may be a good idea because this handler gets called if you exit main the normal way and (as far as I now) if your program gets interrupted via a kill-signal.
How can the compiler be sure that you aren't going to recursively call main()?
Most likely main is just compiled in the same was as a standard function. In C it pretty much needs to be because you might call it from somewhere.
Note that in C++ it's illegal to call main recursively so a c++ compiler might be able to optimize this more. But in C as your question stated it's legal (if a bad idea) to call main recursively so it needs to be compiled in the same way as any other function.
How can this state saving be prevented?
The only thing you can do is to write you own C-Startup routine. That means messing with assembler, but you can then JUMP to your main() instead of just CALLing it.
In my tests with avr-gcc 4.3.5, it only saves registers if not optimizing much. Normal levels (-Os or -O2) cause the push instructions to be optimized away.
One can further specify in a function declaration that it will not return with __attribute__((noreturn))
. It is also useful to do full program optimization with -fwhole-program.
The initial code in avr-libc does use call to jump to main, because it is specified that main may return, and then jumps to exit (which is declared noreturn and thus generates no call). You could link your own variant if you think that is too much. exit() in turn simply disables interrupts and enters an infinite loop, effectively stopping your program, but not saving any power. That's four instructions and two bytes of stack memory overhead if your main() never returns or calls exit().