C语言:通过栈/寄存器返回值问题

19
我刚学习C语言,有一件事情我不太明白。当一个函数返回的结果尺寸不超过寄存器大小时,我的编译器会将其放在EAX中返回。但是当我返回一个大型结构体(不是指针而是结构体本身)时,它会通过堆栈返回。
我的问题是:编译器如何知道如何调用另一个对象导出的函数呢?虽然有一些调用约定(例如stdcall),但这只关乎传递参数,而不是读取返回值,对吗?
应该有一些规则,比如“如果返回值声明为大于EAX,则从[bp-...]中提取”。
还有一个问题:是否可以说,我要返回的大于寄存器大小的对象应该存储在堆中,并通过指针返回以避免所有的堆栈操作?
谢谢。
2个回答

19

函数的返回值是如何传递给调用者的也是函数调用约定的一部分。请参见此处

例如,关于cdecl

 

cdecl调用约定被许多x86架构的C系统使用。在cdecl中,函数参数按从右到左的顺序推送到堆栈上。函数返回值存储在EAX寄存器中(浮点数值除外,它们存储在x87寄存器ST0中)。

[...]
有一些关于cdecl的解释存在不同之处,特别是在如何返回值方面。因此,即使使用相同的cdecl约定并且不调用底层环境,为不同操作系统平台和/或不同编译器编译的x86程序也可能不兼容。一些编译器将长度不超过2个寄存器的简单数据结构返回到EAX:EDX中,而需要特殊处理(例如已定义的构造函数、析构函数或赋值)的较大结构和类对象则在内存中返回。为了“在内存中”传递,调用者分配内存并将其指针作为隐藏的第一个参数传递;调用方填充内存并返回指针,在返回时弹出隐藏的指针。 如果在堆上分配内存,堆栈操作会比必要的堆操作快得多,因此堆栈始终更快。唯一的原因(在C中),你可能想返回指向堆上某个东西的指针是因为它无法放在堆栈上。 澄清: 在上面的最后一句中,“你可能想...”不能被解释为“通常没有理由返回指针”。我是指“如果可以不返回指针实现需求,那么决定仍要使用指针的唯一原因是...”。 当然,像克里斯在他自己的答案中所述,有许多合理的原因可以从函数返回指针,但我只谈论那些你不需要这样做的情况。 换句话说,只有在可以时通过值返回;必须使用指针时才使用指针

在C语言中,你想要返回指向堆上的某个内容的指针的唯一原因是它不适合于栈上。虽然这并不完全正确,但我仍给了你一个+1。 - Chris Lutz
@Chris:我觉得我在那里表达意图时出现了误解。但是,我还是要给你点赞,因为你的回答很有思考性,并且你的评论也让我改进了我的回答。 - Jon
我觉得你说的那句话可能不是你本意。我只是想提醒你一下。 - Chris Lutz
"堆栈操作将比堆操作快得多,为什么?" - flow2k
2
@flow2k 大致上:栈的组织方式使其可以被快速操作;但你需要接受在其上分配的内存相对生命周期受到限制的代价。堆没有这样的限制,但你需要接受更多簿记成本的代价。 - Jon

4
还有一个问题:如果我想返回的对象大于寄存器,那么将其存储在堆中并通过指针返回以避免所有栈操作是否正确?
这可能是正确的。老实说,“通过指针返回”或“通过值返回”的选择应该有更好的理由,而不仅仅是“我希望返回更快”。例如,对于大型对象,通过指针返回比通过栈返回更快,但这并没有考虑在堆上分配对象所需的更长时间。
更重要的是,通过指针返回允许您拥有不透明指针、可变大小的对象和某些程度的多态行为,在栈对象中是不可能的。如果您需要这些类型的行为,无论如何都应该使用通过指针返回。如果不需要,可以使用通过值返回,或者将由用户分配(任何方式)的对象的指针作为参数传递,并在函数中修改该参数(有时称为“输出参数”或类似名称)。
根据您的需要和代码执行情况选择返回方法,而不是认为哪个更快。如果发现绝对需要速度(在剖析并发现返回是瓶颈之后),那么再考虑这种微观优化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接