浅谈C语言函数调用参数压栈的相关问题

 更新时间:2016年09月25日 20:08:06   投稿:jingxian  
下面小编就为大家带来一篇浅谈C语言函数调用参数压栈的相关问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

参数入栈的顺序

以前在面试中被人问到这样的问题,函数调用的时候,参数入栈的顺序是从左向右,还是从右向左。参数的入栈顺序主要看调用方式,一般来说,__cdecl 和__stdcall 都是参数从右到左入栈。

看下面的代码:

#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的系统下的运行结果是:

address of a 1ec62c. 
address of b 1ec628.

32位Ubuntu的结果是:

address of a bfd03290. 
address of b bfd03294.

可以看出,首先,不同的体系结构,栈增长的方向也不同,有的是从低地址向高地址方向增长,有的是从高地址向低地址方向增长。

可以用以下的代码来判断栈的增长方向:

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;
    }
  }
}

函数调用时栈里都有什么

以参数从左到右入栈为例:

push arg0 -- High Address
push arg1
...
push argn
push eip
push ebp -- Low address

32位系统和64位系统函数调用时,参数入栈方式有不同么?

这个问题在不久之前被人问题,当时傻了,我一直以来只关注过32位系统的参数入栈方式,一直以为64位系统也是一样,没有什么不同,现在归纳起来有两点:

64位系统先把传入参数放在寄存器里面,在被调函数的具体实现中把寄存器的值入栈,然后再去栈中取参数

64位系统栈中参数存放的顺序是从左至右的(因为先经历了寄存器传值)

看下面的反汇编:


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后入栈。

 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中。

 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入栈,这就是为什么函数看起来像是从左到右入栈的原因了。

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语言函数调用参数压栈的相关问题的全部内容了,希望对大家有所帮助,多多支持脚本之家~

相关文章

  • C语言实现简单五子棋游戏

    C语言实现简单五子棋游戏

    这篇文章主要为大家详细介绍了C语言实现简单五子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • c++ 预处理的图灵完备之引言

    c++ 预处理的图灵完备之引言

    这篇文章主要介绍了c++ 预处理的图灵完备之引言,需要的朋友可以参考下
    2017-07-07
  • C++实现双向循环链表

    C++实现双向循环链表

    这篇文章主要为大家详细介绍了C++实现双向循环链表,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-04-04
  • 详解C++11原子类型与原子操作

    详解C++11原子类型与原子操作

    这篇文章主要介绍了C++11原子类型与原子操作的相关资料,帮助大家更好的理解和学习c++,感兴趣的朋友可以了解下
    2020-08-08
  • C语言进度条的实现原理详解

    C语言进度条的实现原理详解

    这篇文章主要介绍了C语言进度条的实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • C语言中结构体(struct)的几种初始化方法

    C语言中结构体(struct)的几种初始化方法

    相信大家都知道struct结构体是C语言中非常重要的复合类型,初始化的方法很多,那么小编下面对这些方法进行总结,便于自己和大家以后查阅,有需要的可以参考借鉴。
    2016-08-08
  • 详解C++编程中的变量相关知识

    详解C++编程中的变量相关知识

    这篇文章主要介绍了详解C++编程中的变量相关知识,是C++入门学习中的基础知识,需要的朋友可以参考下
    2015-09-09
  • C++中函数指针详解及代码分享

    C++中函数指针详解及代码分享

    这篇文章主要介绍了C++中函数指针详解及代码示例,具有一定参考价值,需要的朋友可以了解下。
    2017-10-10
  • c++ Qt信号槽原理

    c++ Qt信号槽原理

    这篇文章主要介绍了c++ Qt信号槽原理的相关资料,帮助大家更好的理解和使用c++,感兴趣的朋友可以了解下
    2021-02-02
  • 关于C语言中弱符号与弱引用的实际应用问题

    关于C语言中弱符号与弱引用的实际应用问题

    在编码过程中,我们经常遇到符号重定义的错误问题,本文通过实例代码展示给大家介绍了C语言弱符号与弱引用的实际应用问题,一起看看吧
    2021-09-09

最新评论