为什么 avr-gcc 在调用 main() 时要费心保存寄存器状态?
avr-gcc 程序中的 main() 函数将寄存器状态保存在堆栈上,但是当运行时调用它时,我知道在微控制器上没有任何可返回的内容。这是浪费内存吗?如何防止这种状态保存?
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
编译器如何确定您不会递归调用 main()?
How can the compiler be sure that you aren't going to recursively call main()?
这都是关于 C 标准的。
没有什么可以阻止你在某个时候退出 main。你的程序中可能不会这样做,但其他人可能会这样做。
此外,您可以通过atexit运行时函数注册清理处理程序。这些函数需要定义的寄存器状态才能正确执行,保证这一点的唯一方法是保存和恢复 main 周围的寄存器。
这样做甚至可能有用:
我不知道 AVR,但其他微控制器在完成工作并等待重置时可以进入低功耗状态。从清理处理程序中执行此操作可能是一个好主意,因为如果您以正常方式退出 main (就我现在而言),如果您的程序通过终止信号中断,则该处理程序会被调用。
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.
很可能 main 只是以与标准函数相同的方式进行编译。在 C 中,它几乎是必需的,因为您可能会从某个地方调用它。
请注意,在 C++ 中递归调用 main 是非法的,因此 C++ 编译器可能能够对此进行更多优化。但在 C 中,正如你的问题所述,递归调用 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.
在我对 avr-gcc 4.3.5 的测试中,如果没有进行太多优化,它只会节省寄存器。正常级别(-Os 或 -O2)会导致推送指令被优化掉。
我们可以在函数声明中进一步指定它不会使用 __attribute__((noreturn)) 返回。使用 -fwhole-program 进行完整的程序优化也很有用。
avr-libc 中的初始代码确实使用 call 跳转到 main,因为它指定了 main may 返回,然后跳转到 exit(声明为 noreturn,因此不会生成任何调用)。如果您认为太多,您可以链接自己的变体。 exit() 反过来只是禁用中断并进入无限循环,有效地停止程序,但不会节省任何电量。如果您的 main() 从未返回或调用 exit(),那么这将是四个指令和两个字节的堆栈内存开销。
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().
您唯一能做的就是编写自己的 C 启动例程。这意味着会弄乱汇编程序,但是您可以跳转到 main() 而不是仅仅调用它。
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.