c++ 函数调用的参数推送顺序不反映堆栈中参数的地址
Parameter push order of a c++ function call does not reflect the parameter's address in stack
我做了一个实验:
class A {
public:
A() {}
A(const A& a) {
printf("A - %pn", this);
}
};
class B {
public:
B() {}
B(const B& b) {
printf("B - %pn", this);
}
};
void func(A a, B b) {}
int main() {
A a;
B b;
func(a, b);
return 0;
}
输出为:
B - 0x7fff636e2c48
A - 0x7fff636e2c50
既然参数是从右向左推的,为什么B的地址比A的要低呢?困惑。(堆栈从较高的地址开始(。
简而言之:func
的参数从右到左正确地"推送"在堆栈上; 您打印的不是这些参数的堆栈地址,而是这些参数的值,这些参数的值恰好也是堆栈上的一些地址。
稍微详细一点...
首先,您在x64机器上。你应该忘记_cdecl
、_stdcall
等调用约定。只有一个调用约定(简化版(:前四个函数参数(在函数调用中从左到右列出(将在寄存器上传递,其余的 - 在堆栈上传递。现在,也就是说,调用方需要在自己的堆栈上分配足够的"家庭空间",因此被调用方可以"溢出"寄存器上传递的参数。因此,原则上,如果被调用方决定通过"家庭空间"使用堆栈,则前四个参数也可以在堆栈上找到。
:被调用方将使用从右到左的顺序将寄存器"溢出"到"主空间"中,这意味着第一个函数参数最终在"主空间"区域中的堆栈上的地址将低于第二个参数。因此,在这方面,参数总是从右到左"推送"在堆栈上。
第三,你的输出与参数传递给func
的方式无关,而与创建临时变量的位置有关。这是发生的事情:main
保留了足够的堆栈空间来创建临时a
和b
; b
首先在main
堆栈上的较低地址创建,这很好,因为函数调用func
中调用函数(在我们的例子中是复制构造函数A(const A&)
和B(const B&)
(的顺序是明确未定义的;临时a
和b
的地址存储在寄存器上(并且已经保留了足够的"家庭空间"func
"溢出"(; func
被称为; func
可以将寄存器"溢出"到"家庭空间"中;如果它"溢出"它们,那么b
的地址将被"溢出"到堆栈地址高于堆栈地址的堆栈地址中,a
的地址将被"溢出"到堆栈地址 - 这是传递参数的从右到左的顺序。
这是一些代码和相应的汇编程序。请注意,我使用了修改后的代码(更多参数和两个函数(来显示如何传递参数。int
参数funcI
函数是为了说明要点,而不会乱七八糟地调用复制构造函数。具有两个参数的函数版本本质上是已发布funcC
的"切片"版本 - 它被"切片"为处理RCX
和RDX
寄存器(到最后一个寄存器(的代码。另外,请注意,RSP
包含堆栈指针和内部funcI
并且funcC
小于RSP
内部main
8
(这解释了检索funcI
时的偏移量funcC
堆栈上推送的最后两个参数(:
class A
{
public:
int mData;
A()
{ // mov qword ptr [rsp+8],rcx ; "spilling" rcx
} // mov rax,qword ptr [rsp+8] ; RAX (return value) = pointer to the object;
A( const A& )
{// mov qword ptr [rsp+10h],rdx ;"spilling" RDX and RCX
// mov qword ptr [rsp+8],rcx
}// mov rax,qword ptr [rsp+8] ; notice, RAX has value of RCX
};
class B
{
public:
int mData;
B()
{ // mov qword ptr [rsp+8],rcx ; "spilling" rcx
} // mov rax,qword ptr [rsp+8] ; RAX (return value) = pointer to the object
B( const B& )
{// mov qword ptr [rsp+10h],rdx ;"spilling" RDX and RCX
// mov qword ptr [rsp+8],rcx
}// mov rax,qword ptr [rsp+8] ; notice, RAX has value of RCX
};
void __cdecl funcI( int a, int b, int c, int d, int e, int g )
{ // mov dword ptr [rsp+20h],r9d ; "spilling" registers, notice right-to-left order
// mov dword ptr [rsp+18h],r8d
// mov dword ptr [rsp+10h],edx
// mov dword ptr [rsp+8],ecx
a = 1; // mov dword ptr [rsp+8],1 ; accessing first 4 "spilled" function parameters
b = 2; // mov dword ptr [rsp+10h],2 ; notice, first function parameter has lower stack address than second parameter etc
c = 3; // mov dword ptr [rsp+18h],3 ; so parameters pushed right-to-left by callee's "spilling"
d = 4; // mov dword ptr [rsp+20h],4 ;
e = 5; // mov dword ptr [rsp+28h],5 ; here, accessing 2 parameters pushed on stack explicitly by caller
g = 6; // mov dword ptr [rsp+30h],6 ; they were pushed right-to-left
// also notice the offset of 8 in RSP ("g" is accessed through [rsp+30h] while it was put into [rsp+28h] in main
} // ret
void __cdecl funcC( A a1, A a2, A a3, A a4, A a5, B b1 )
{// mov qword ptr [rsp+20h],r9 ; "spilling"
// mov qword ptr [rsp+18h],r8
// mov qword ptr [rsp+10h],rdx
// mov qword ptr [rsp+8],rcx
a1.mData = 1;
// mov rax,qword ptr [rsp+8] ; same right-to-left order: a1 itself has lower stack address 'rsp+8' than a2 'rsp+18h'
// mov dword ptr [rax],1 ; HOWEVER, stack address value 'rsp+88h' stored in a1 is HIGHER than stack address value 'rsp+78h' stored in a2!!!!
a2.mData = 2;
// mov dword ptr [rax],2
// mov rax,qword ptr [rsp+18h]
a3.mData = 3;
// mov rax,qword ptr [rsp+18h]
// mov dword ptr [rax],3
a4.mData = 4;
// mov rax,qword ptr [rsp+20h]
// mov dword ptr [rax],4
a5.mData = 5;
// mov rax,qword ptr [rsp+28h]
// mov dword ptr [rax],5
b1.mData = 6;
// mov rax,qword ptr [rsp+30h]
// mov dword ptr [rax],6
} // ret
int main( void )
{
// sub rsp,0C8h ; reserving stack for `main`
A a;
// lea rcx,[rsp+30h] ; putting into RCX stack address of local 'a'
// call A::A()
B b;
// lea rcx,[rsp+34h] ; putting into RCX stack address of local 'b'
// call B::B()
funcI( 1, 2, 3, 4, 5, 6 );
// mov dword ptr [rsp+28h],6 ; passing parameters to `funcI`
// mov dword ptr [rsp+20h],5 ; last 2 on stack, first 4 on registers
// mov r9d,4 ; notice the right-to-left order:
// mov r8d,3 ; "6" is put on stack at higher address than "5" etc.
// mov edx,2 ; notice also that "6" is put into [rsp+28h]: during call to `funcI` RSP will be less by 8
// mov ecx,1 ; and inside `funcI` parameter will be accessed accordingly through [rsp+30h]
// call funcI
funcC( a, a, a, a, a, b );
// lea rax,[rsp+38h] ; some preparations: putting stack addresses of temporaries into stack variables
// mov qword ptr [rsp+40h],rax ; there are few indirections in debug mode, we can ignore them, noticing the addresses
// lea rax,[rsp+48h]
// mov qword ptr [rsp+50h],rax
// lea rax,[rsp+58h]
// mov qword ptr [rsp+60h],rax
// lea rax,[rsp+68h]
// mov qword ptr [rsp+70h],rax
// lea rax,[rsp+78h]
// mov qword ptr [rsp+80h],rax
// lea rax,[rsp+88h]
// mov qword ptr [rsp+90h],rax
// lea rdx,[rsp+34h] ; putting into RDX stack address of local 'b'
// mov rcx,qword ptr [rsp+40h] ; putting into RCX stack address of temporary - this is right-most temporary of type `B` in `funcC`:
// call B::B(const B&) ; [rsp+40h] = (rsp+38h) which is stack address of temporary `b`
// mov qword ptr [rsp+98h],rax ; putting on stack value of RAX: [rsp+98h] now contains (rsp+38h)
// lea rdx,[rsp+30h]
// mov rcx,qword ptr [rsp+50h]
// call A::A(const B&)
// mov qword ptr [rsp+0A0h],rax
// lea rdx,[rsp+30h]
// mov rcx,qword ptr [rsp+60h]
// call A::A(const A&)
// mov qword ptr [rsp+0A8h],rax
// lea rdx,[rsp+30h]
// mov rcx,qword ptr [rsp+70h]
// call A::A(const A&)
// mov qword ptr [rsp+0B0h],rax
// lea rdx,[rsp+30h]
// mov rcx,qword ptr [rsp+80h]
// call A::A(const A&)
// mov qword ptr [rsp+0B8h],rax
// lea rdx,[rsp+30h]
// mov rcx,qword ptr [rsp+90h]
// call A::A(const A&) ; notice, RAX is not copied onto stack, it's preserved (see below)
// mov rcx,qword ptr [rsp+98h] ; passing parameters (ignoring indirections): putting stack address of right-most temporary `b` onto stack
// mov qword ptr [rsp+28h],rcx ; notice stack address 'rsp+28h' where stack address of 'b' 'rsp+38h' is put
// mov rcx,qword ptr [rsp+0A0h] ; same for right-most temporary of type 'A' (indirection again)
// mov qword ptr [rsp+20h],rcx ; stack address 'rsp+20h' where stack address 'rsp+48h' of right-most `a` is put
// mov rcx,qword ptr [rsp+0A8h] ; NOW: this is RIGHT-TO-LEFT order - take a closer look:
// mov r9,rcx ; rsp+28h > rsp+20h but value [rsp+28h] = rsp+38h < rsp+48h = [rsp+20h]
// mov rcx,qword ptr [rsp+0B0h] ; parameters are pushed right-to-left, parameters' values are not ordered this way (and, really, can be anything)
// mov r8,rcx ; other 4 parameters are put on registers
// mov rcx,qword ptr [rsp+0B8h]
// mov rdx,rcx
// mov rcx,rax ; notice, preserved RAX is put into RCX - it has stack address of first temporary of type 'A'
// call funcC
return ( 0 ); // xor eax,eax ; return value is 0
}
// add rsp,0C8h ; restoring stack
// ret
因为你把这些对象推到堆栈上,堆栈通常会向下增长。因此,这不是有保证的行为,并且特定于平台。不过,这与如何将参数传递给函数无关。
- 如何使用主参数添加更多堆栈?
- 如何从 x64 程序集中的堆栈中获取参数?
- C++ 参数堆与堆栈,基元类型的数组
- 将参数推送到调用堆栈 (C++) 的可移植方法
- 变量参数列表 后面的'const std::string&'弄乱了堆栈
- 评估虚拟堆栈中的可变参数
- 被调用方如何知道参数是通过寄存器而不是堆栈传递的
- 在 Myfile.exe 中0x00831D39时未处理的异常:0xC00000FD:堆栈溢出(参数:0x0000000
- 如何使用ABI调用过程在堆栈上正确传递Float参数
- C++17 使用选定的构造函数在堆栈中构造数组(每个数组条目的构造函数参数值相同)
- 通过参考数组传递分配的堆栈参数
- 系统堆栈中的两个函数的递归调用(将不同数量的数组作为参数传递)有什么区别
- 功能参数导致堆栈溢出
- 使用 windbg 查找不在堆栈顶部的函数的函数参数
- 参数列表太长了,而堆栈大小ID无限和命令大小约为300 kbyte
- 如何使用在另一个类的构造函数中的堆栈上接受参数的构造函数创建对象
- 指针函数参数已损坏,堆栈已损坏
- stl数据结构的堆栈上输出参数与返回值的效率
- 数组使用的堆栈(参数传递/返回)
- 为变元元组构造参数堆栈