浅谈C语言函数调用参数压栈的相关问题_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语言函数压栈顺序、python 调用c语言、c语言函数的调用、c语言函数调用过程,以便于您获取更多的相关知识。

时间: 2024-12-30 20:15:20

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

浅谈C++内存分配及变长数组的动态分配_C 语言

第一部分 C++内存分配 一.关于内存 1.内存分配方式 内存分配方式有三种: (1)从静态存储区域分配.内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在 例如全局变量,static变量. (2)在栈上创建.在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存 储单元自动被释放.栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限. (3) 从堆上分配,亦称动态内存分配.程序在运行的时候用malloc或new申请任意多少的内存,程序员

如何用C语言、Python实现栈及典型应用_C 语言

前言 栈是什么,你可以理解为一种先入后出的数据结构(First In Last Out),一种操作受限的线性表... C实现 借助与C语言中的void指针及函数指针,我们可以实现一个链式通用栈: /* stack.h */ #ifndef _STACK_H_ #define _STACK_H_ typedef struct stackNode { void *value; struct stackNode *next; } stackNode; typedef struct stack { st

浅谈c/c++中使用指针需要注意的问题_C 语言

一.使用指针的时候需要注意几点: • 分配空间 • 初始化 • 释放 二.常见的错误有几种: 1)内存分配未成功,却使用了它 编程新手常犯这种错误,因为他们没有意识到内存分配会不成功.常用解决办法是,使用内存之前检查指针是否为Null. 如果指针p是函数的参数,那么在函数的入口处用assert(p != NULL)进行检查.如果使用malloc或new来申请内存,应该用if(p == NULL)或if(p != NULL)进行放错处理. 2)内存分配虽然成功,但是尚未初始化就引用它 犯这种错误主

浅谈C++日志系统log4cxx的使用小结详解_C 语言

本文主要从log4cxx级别.layout.格式化.命名规则.Filter几个方面介绍. 一.log4cxx命名规则Logger由一个String类的名字识别,logger的名字是大小写敏感的,且名字之间具有继承的关系,子名有父名作为前缀,用点号.分隔.如:x.y是x.y.z的父亲.根logger (root logger)是所有logger的祖先, 它具有如下属性:1) 它总是存在的:2) 它不可以通过名字获得.通过调用public static Logger Logger.getRootLo

浅谈C++中的string 类型占几个字节_C 语言

在C语言中我们操作字符串肯定用到的是指针或者数组,这样相对来说对字符串的处理还是比较麻烦的,好在C++中提供了 string 类型的支持,让我们在处理字符串时方便了许多. 首先,我写了一段测试代码,如下所示: 复制代码 代码如下: #include <iostream>using namespace std; int main(void){ string str_test1; string str_test2 = "Hello World";  int value1, va

关于函数调用方式__stdcall和__cdecl详解_C 语言

关于函数调用方式__stdcall和__cdecl详解 __stdcall __cdecl 两者的相同点与不同点 实例 __stdcall __stdcall的全称是standard call.是C++的标准调用方式. 函数参数的入栈顺序为从右到左入栈.函数返回时使用retn x指令,其中x为调整堆栈的字节数.这种方式叫做自动清栈.即被调用的函数的参数个数是固定的,调用者必须严格按照定义传递参数,一个不多,一个不少. __cdecl __cdecl的全称是C Declaration,即C语言默认

C语言对栈的实现基本操作_C 语言

c语言中栈是一种数据结构,后进先出,即最后进入栈的数据最先弹出.c语言中没有栈这种数据类型,需要自己编程构建.下面我们就一起来了解一下c语言中栈的基本操作. C语言对栈的实现基本操作,操作如下: #include <stdio.h> #include <malloc.h> #include <stdlib.h> #include <stdbool.h> typedef struct Node { int data; struct Node * pNext;

浅谈Bootstrap的DatePicker日期范围选择_AJAX相关

用日期插件时,经常会有一种需求.两个input框选择.开始时间小于结束时间,结束时间大于开始时间,开始时间和结束时间都不大于当前时间. 我们当然可以用选择的结果来判断输入正确与否.但是更好的办法是让我们的日期选择插件做出一些限制. Bootstrap搭配了很优秀的日期选择插件.DatePicker和DateTimePicker. 两者功能很类似.使用方法也是差不多的.DatePicker支持更多的事件和设置. 看api知道日期变化的时候会有一个事件changeDate.当选择的日期变化的时候,会

C++基于栈实现铁轨问题_C 语言

本文实例讲述了C++基于栈实现铁轨问题.分享给大家供大家参考.具体分析如下: 示例图如下所示: 经典栈问题!第一次做的时候思路太混乱了,现在看了刘汝佳的书,重新整理下. #include <stdio.h> #include <string.h> /****************************************************************** * 用数组A存储调整前的车厢号序列,用数组B存储调整好的车厢号序列 * 用栈stack存储存放在中转站