如何坚持C编译器将局部变量放在堆栈上,而不是寄存器中

时间:2015-03-08 10:08:19

标签: c gcc garbage-collection clang tcc

我正在尝试将历史功能语言解释器(EMAS的KRC)移植到现代系统(C for Unix),并且它有一个垃圾收集器,它希望能够扫描堆栈以获取指向堆的指针以了解哪些指针在GC期间移动堆中的对象时,它必须重定位。为此,必须在堆栈中找到指向堆的所有函数参数和局部变量。

现在,有一段时间“register”关键字意味着“你可以把这个变量放在寄存器中,如果你喜欢”,否则它就在堆栈上,但现在所有(GCC,Clang,Tinyc / tcc)C编译器似乎无论如何都将局部变量放入寄存器中,无法禁用此行为,结果是GC错过了属于正在进行的函数的某些值,无法保留它们并破坏堆。

有没有办法告诉任何这些编译器使用原始C语义,除非你说“注册”,否则所有局部变量都在堆栈上?

我有一些狡猾的“解决方案”:

  • 在任何地方添加额外的代码以获取每个面向堆的局部变量的地址并将其传递给虚函数,作为强制它在内存位置的一种方式;
  • 使所有静态函数全局化,以避免函数内联以及内联函数参数的结果优化;
  • 将GC()函数括在一个存根中,该存根将所有机器寄存器推入堆栈,调用真正的GC()函数然后弹出它们;

这些似乎都可以改善问题,但是非常黑客和不可靠。

是否有更好的方法来实现所需的结果,确保所有函数参数和局部变量都在堆栈中?

4 个答案:

答案 0 :(得分:3)

好的,这是一个奇怪的GC;好吧,您可能会使用volatile关键字。

它最初用于内存映射设备,您希望强制编译器不要优化变量。它的使用和abuse一直是讨论的主题。

  

是否有更好的方法来实现所需的结果

真的很难回答。一方面:显然,是的:不要让你的GC依赖于那些无法依赖的东西。但这意味着重写它。另一方面:如果需要额外的代码来确保堆栈放置工作,那么为什么不去寻找呢?它并不像您为效果代码移植历史解释器。

答案 1 :(得分:3)

我想你使用了一种“标记和扫描”GC。在这种情况下,您只需在标记阶段开始时保存寄存器。我的建议是检查你的GC,找到“标记和扫描”操作开始的地方,并把代码放在一个可访问的内存中。 setjmp是实现此目的的半便携式方式(除非您正在使用sparc)。

答案 2 :(得分:0)

对你的问题似乎有一个简单的解决方案:如果GC同步运行,例如,如果从分配函数调用,那么只要所有寄存器,其他函数是否在堆栈或寄存器中存储指针并不重要在GC运行之前的某个时刻保存到堆栈并扫描堆​​栈。将GC代码包装在一个保存堆栈中所有寄存器的功能中,您就完成了。内联汇编可能是必要的,但是setjmp应该足够了,如Marian所述。

答案 3 :(得分:0)

我想我会告诉你它是如何被淘汰出局的。我在代码中发现了几个没有被告知GC的数据项,在进入GC之前将寄存器放在堆栈上,并且最后一些是堆栈变量指向“尾部”堆中的链表的单元格而不是头部,以便能够快速追加。我忽略了这些因为没有与细胞边界对齐。在这些事情中,我永远不会有setjmp()技巧,非常感谢。你的建议使得interp工作与否之间产生了差异。祝福!