参数入栈的顺序
以前在面试中被人问到这样的问题,函数调用的时候,参数入栈的顺序是从左向右,还是从右向左。参数的入栈顺序主要看调用方式,一般来说,__cdecl 和__stdcall 都是参数从右到左入栈。
看下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#include <stdio.h>
int test( int a, int b)
{
printf ( "address of a %x.\n" , &a);
printf ( "address of b %x.\n" , &b);
return 0;
}
int main()
{
test(1, 2);
return 0;
}
|
在64位Ubuntu的系统下的运行结果是:
1
2
|
address of a 1ec62c.
address of b 1ec628.
|
32位Ubuntu的结果是:
1
2
|
address of a bfd03290.
address of b bfd03294.
|
可以看出,首先,不同的体系结构,栈增长的方向也不同,有的是从低地址向高地址方向增长,有的是从高地址向低地址方向增长。
可以用以下的代码来判断栈的增长方向:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
typedef enum {
LOW_TO_HIGH,
HIGH_TO_LOW,
LEFT_TO_RIGHT,
RIGHT_TO_LEFT,
}stack_direc_t;
int stack_grow_direc()
{
static char *p = NULL;
char c;
if (p == NULL) {
p = &c;
stack_grow_direc();
}
else {
printf ( "First in stack address is %x.\n" , p);
printf ( "Second in stack address is %x.\n" , &c);
if (&c > p) {
printf ( "Stack grows from low address to high address!\n" );
return LOW_TO_HIGH;
}
else {
printf ( "Stack grows from high address to low address!\n" );
return HIGH_TO_LOW;
}
}
}
|
函数调用时栈里都有什么
以参数从左到右入栈为例:
1
2
3
4
5
6
|
push arg0 -- High Address
push arg1
...
push argn
push eip
push ebp -- Low address
|
32位系统和64位系统函数调用时,参数入栈方式有不同么?
这个问题在不久之前被人问题,当时傻了,我一直以来只关注过32位系统的参数入栈方式,一直以为64位系统也是一样,没有什么不同,现在归纳起来有两点:
64位系统先把传入参数放在寄存器里面,在被调函数的具体实现中把寄存器的值入栈,然后再去栈中取参数
64位系统栈中参数存放的顺序是从左至右的(因为先经历了寄存器传值)
看下面的反汇编:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
C代码同上面一样
Ubuntu 32位反汇编:
int main()
{
804846d: 55 push %ebp
804846e: 89 e5 mov %esp,%ebp
8048470: 83 e4 f0 and $0xfffffff0,%esp
8048473: 83 ec 10 sub $0x10,%esp
test(1, 2);
8048476: c7 44 24 04 02 00 00 movl $0x2,0x4(%esp)
804847d: 00
804847e: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048485: e8 8a ff ff ff call 8048414 <test>
return 0;
804848a: b8 00 00 00 00 mov $0x0,%eax
}
int test( int a, int b)
{
8048414: 55 push %ebp
8048415: 89 e5 mov %esp,%ebp
8048417: 83 ec 18 sub $0x18,%esp
printf ( "address of a %x.\n" , &a);
804841a: b8 60 85 04 08 mov $0x8048560,%eax
804841f: 8d 55 08 lea 0x8(%ebp),%edx
8048422: 89 54 24 04 mov %edx,0x4(%esp)
8048426: 89 04 24 mov %eax,(%esp)
8048429: e8 12 ff ff ff call 8048340 < printf @plt>
return 0;
8048466: b8 00 00 00 00 mov $0x0,%eax
}
Ubuntu 64位反汇编:
int main()
{
40056e: 55 push %rbp
40056f: 48 89 e5 mov %rsp,%rbp
test(1, 2);
400572: be 02 00 00 00 mov $0x2,%esi
400577: bf 01 00 00 00 mov $0x1,%edi
40057c: e8 ac ff ff ff callq 40052d <test>
return 0;
400581: b8 00 00 00 00 mov $0x0,%eax
}
int test( int a, int b)
{
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 48 83 ec 10 sub $0x10,%rsp
400535: 89 7d fc mov %edi,-0x4(%rbp)
400538: 89 75 f8 mov %esi,-0x8(%rbp)
printf ( "address of a %x.\n" , &a);
40053b: 48 8d 45 fc lea -0x4(%rbp),%rax
40053f: 48 89 c6 mov %rax,%rsi
400542: bf 14 06 40 00 mov $0x400614,%edi
400547: b8 00 00 00 00 mov $0x0,%eax
40054c: e8 bf fe ff ff callq 400410 < printf @plt>
return 0;
400567: b8 00 00 00 00 mov $0x0,%eax
}
|
看32位的ubuntu操作系统, 8048476: 的确是把参数直接入栈,2先入栈,1后入栈。
1
2
3
4
|
8048476: c7 44 24 04 02 00 00 movl $0x2,0x4(%esp)
804847d: 00
804847e: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048485: e8 8a ff ff ff call 8048414 <test>
|
再来看64位的ubuntu操作系统,2 和1根本就没有放入到栈中,而是放到了寄存器esi和edi中。
1
2
3
4
5
|
40056f: 48 89 e5 mov %rsp,%rbp
test(1, 2);
400572: be 02 00 00 00 mov $0x2,%esi
400577: bf 01 00 00 00 mov $0x1,%edi
40057c: e8 ac ff ff ff callq 40052d <test>
|
再来看64位系统test的实现,先把edi入栈,再把esi入栈,这就是为什么函数看起来像是从左到右入栈的原因了。
1
2
3
4
5
|
40052d: 55 push %rbp
40052e: 48 89 e5 mov %rsp,%rbp
400531: 48 83 ec 10 sub $0x10,%rsp
400535: 89 7d fc mov %edi,-0x4(%rbp)
400538: 89 75 f8 mov %esi,-0x8(%rbp)
|
以上就是小编为大家带来的浅谈C语言函数调用参数压栈的相关问题的全部内容了,希望对大家有所帮助,多多支持服务器之家~