考虑以下不会内联的函数并假设 x86 作为平台:
void doSomething(int & in){
//do something
}
首先,我不确定这种情况是否会发生,但因为我认为有可能,所以我会问,如果在任何调用者中调用此函数时,要提供的参数恰好位于调用者堆栈帧的顶部,以便在被调用函数中,可以通过 ebp 寄存器(在被调用者将 esp 的内容移入 ebp 后)以汇编语言访问该函数,您是否建议我们完全忽略函数的参数声明,并在这种特殊情况下使用汇编来访问我们的参数,或者只留下功能定义原样并将其留给编译器执行其功能?因为我没有在任何地方读过编译器会将这种特殊情况视为调用约定的一个因素,并且我认为它只会生成代码以将指向参数的指针传递给被调用者堆栈帧或寄存器之一
首先,这很容易被破坏 - 例如,您获得不同版本的编译器,它会生成不同的代码。或者您更改优化功能。不要介意突然需要在不同的地方使用
doSomething
然后它就不起作用的情况,因为变量不再位于堆栈顶部。
其次,假设函数内的代码足够短,编译器很可能会内联该函数,因此您根本不会“丢失”任何内容。
第三,现代编译器中的单个参数通常在寄存器中传递,因此启用优化时没有任何好处。
如果您确实认为这样做有有价值的好处,并且编译器不会内联或以其他方式优化代码[您查看了生成的代码吗?],那么请尝试使用
forceinline
或 always_inline
或任何名称在你的编译器中(大多数编译器都有这样的选项)。如果这不起作用,请使用宏手动内联它。或者简单地将代码移动到“复制粘贴”调用的位置。
您的注释“要提供的参数恰好位于调用者堆栈帧的顶部,因此在被调用函数中通过 ebp 寄存器访问该参数”包含事实误解。
这是因为以下几点:
push
放入堆栈。但一般情况并非如此。即使在 32 位 x86 上,也存在非基于堆栈的调用约定(例如,Windows
call
或 32 位 Linux 内核中使用的 GNU GCC 调用约定)。如果使用这样的参数,则参数不会在堆栈顶部找到,而是在......任何用于保存第一个参数的寄存器中找到。
但即使你有基于堆栈的参数传递......仍然:
您至少错过了 x86 上的
fastcall
推入堆栈顶部,因此当函数的第一条指令到达该方式正在执行时,
call
将不是指向该函数的第一个参数,而是指向返回地址。
您错过了
ESP
设置它。因此,想要使用它的函数(即使只是作为帧指针)必须在使用它之前将其保存在某个地方。这意味着正常的序言将有
EBP
(你不能仅做
push EBP; mov EBP, ESP
,因为这会覆盖调用者的
MOV EBP, ESP
,这是无效的/你可能不会这样做)。因此,如果您想引用函数的第一个参数,则需要 EBP
not
[ EBP + 8 ]
。
如果您not使用帧指针,那么第一个参数(由于
[ EBP ]
用于到达已推送返回地址的函数)位于call
not[ ESP + 4 ]
。
我希望这能澄清一点。
不,我不会。调用约定可能有所不同(x86 和 x86_64 之间);参数可以被推入堆栈或放入寄存器,我不确定您是否能确定它们在哪里。