C语言函数调用中堆栈知识

原创 2016年05月30日 21:09:19

C语言的程序运行可以说就是不断的调用函数,从主入口的main函数到各种各样的库函数,再到用户自定义的完成特定功能的函数。
程序中关于一个函数的操作主要包括三个方面。①函数声明,②函数定义,③函数调用。简而言之,函数声明顾名思义就是告诉编译器有一个这样的函数,同时告诉编译器它的返回值类型和参数类型(参数缺省表示参数还没具体给出)。函数定义就是定义一个函数要执行那些特定的操作。而函数调用就是让程序在恰当的时候去执行行该函数定义的这些操作。
接下来主要讨论函数在调用过程中,参数在堆栈中的情况。
在X86平台下,C语言函数调用的过程是这样的。首先把参数从后往前压入栈中(从后往前,这样在执行部分就可以直接从栈顶一个一个的顺序取出来)。(至于汇编部分的其他细节不讨论,其他地址指针也不是本文的重点)。在程序执行的时候,就从栈顶把一个一个把相应的数据拿出来,赋值给变量。
如此,在传入参数和函数实际参数类型或数量不相等时,就会出现一些问题。如下,简单举一例。

#include <stdio.h>
typedef long long ll;

void ptchar();//声明

int main()
{
    ptchar((ll)0x7070707020202020);//调用
    return 0;
}

void ptchar(a1)//定义
int a1;//☆
{
    printf("%x\n", a1);
}

在程序中,主程序向mian函数传入的参数是一个long long类型,它占八个字节,而函数中实际接收的是一个int类型,占四个字节。☆处据说是一种老式的写法,和新写法是一样一样的。
那么这个程序输出结果是多少呢?”20202020”,为啥?7070707070部分数据哪去了呢?实际上70707070也被压入了栈中,但是由于函数实际只取出一个int(四字节)所以就拿不到70707070部分的数据了。
那为什么拿到的是20202020,而不是70707070呢?以下是我个人理解,如有错误请大神指正。在PC上,使用的是小端字节序,这意味着低数位的数据被放在低位地址中,高数位的数据被放在高位地址中。而栈顶对应的是低地址,栈低对应的是高地址,所以是低数位对应的数据先出栈,高数位对应的数据后出栈,因此上面的例子中只取到了20202020。如果想取到70707070,只要多加一个接受的参数int a2就可以了。
那么如果传入的参数多余实际的参数会如何呢?如下:

#include <stdio.h>
typedef long long ll;

void ptchar();

int main()
{
    ptchar((ll)0x7070707020202020);
    return 0;
}

void ptchar(a1,a2,a3)
int a1,a2,a3;
{
    printf("%x %x %x\n", a1,a2,a3);
}

它的输出是“20202020 70707070 3f”,这里3f是一个我看不懂的数据,确实,他是栈里面表示其他的含义的数据,不过这里被取到了,因为实际参数多于传入的参数,就会往栈的下面继续取数。
还有碰到传小数的情况要注意,函数在传递的过程中会把float(4字节)转为double(八字节),所以传递过程中小数统统占8个字节。
小插曲:
以下代码分别输出啥? 可以自己在32 和 64的环境下测试一下。

    char c='a';
    printf("%d\n", sizeof(char));
    printf("%d\n", sizeof('a'));
    printf("%d\n", sizeof(c));

结果是1,4,1,为何?char不是一个字节吗?其实’a’相当于是int的97,所以成四个字节了。我也是看了网上别人说的才知道的。

接下来转战X86_64的环境。
参数在堆栈中整个过程基本上是一致的。
但是,如果试一下下面的代码,就会发现有问题出现了。

#include <stdio.h>
typedef long long ll;

void ptchar();

int main()
{
    ptchar((ll)0x7070707020202020);
    return 0;
}

void ptchar(a1,a2)
int a1,a2;
{
    printf("%x %x\n", a1,a2);
}

我运行的结果是”20202020 c1b2df88”,前面20202020显然已经说过了,但是后面出现了一个不能理解的数字,而且重新运行一下程序,后面的数字变了。显然这不是调用函数传过来的,而是拿了堆栈里面的一个数据。
为什么呢?显然堆栈并没有把long long八个字节传过来,只传过来四个字节。
然后我们再看看下面这段代码:

#include <stdio.h>
typedef long long ll;

void ptchar();

int main()
{
    ptchar(1,2,3,4,5,6,(ll)0x7070707020202020);
    return 0;
}

void ptchar(a1,a2,a3,a4,a5,a6,a7)
int a1,a2,a3,a4,a5,a6,a7;
{
    printf("%x %x %x %x %x %x %x\n", a1,a2,a3,a4,a5,a6,a7);
    int* p = &a7;
    printf("%x\n", *(p+1));
}

试试结果是啥?
1 2 3 4 5 6 20202020
70707070
看,70707070出来了,数据就再20202020的后面的地址中。
那为什么第一次不行呢?(其实第一次用取地址的方法也取不到值)
这是因为,在x64平台下面,前6个参数通过64位寄存器来传递,相当于是寄存器直接把值赋值给了int,显然高位就丢了。后面的例子,long long位于第七个参数,并不是通过寄存器传递的,而是通过压栈传递的,自然我们就可以通过取地址的方法获得其值了。好累,去休息休息。

参考:http://www.360doc.com/content/15/0105/11/982782_438311997.shtml

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

C语言函数调用及栈帧结构

一、地址空间与物理内存 (1)地址空间与物理内存是两个完全不同的概念,真正的代码及数据都存在物理内存中。 物理储存器是指实际存在的具体储存器芯片,CPU在操纵物理储存器的时候都把他们当做内存来对待...

C++堆栈详解

一、预备知识—程序的内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。...

为何C语言(的函数调用)需要堆栈,而汇编语言却不需要堆

之前看了很多关于uboot的分析,其中就有说要为C语言的运行,准备好堆栈。 而自己在Uboot的start.S汇编代码中,关于系统初始化,也看到有堆栈指针初始化这个动作。但是,从来只是看到有人说...

为何C语言(的函数调用)需要堆栈,而汇编语言却不需要堆栈

为何C语言(的函数调用)需要堆栈,而汇编语言却不需要堆栈 之前看了很多关于uboot的分析,其中就有说要为C语言的运行,准备好堆栈。 而自己在Uboot的start.S汇编代码中,...

堆栈-栈帧-函数调用过程分析

  • 2011年11月24日 21:51
  • 51KB
  • 下载

C语言中函数调用中的传值与传址

首先介绍一下函数中传值与传址的概念: 传值:传值,实际是把实参的值赋值给行参,相当于copy。那么对行参的修改,不会影响实参的值 。 传址: 实际是传值的一种特殊方式,只是他传递的是地址,不是普通...

C语言函数调用

  • 2011年10月29日 23:36
  • 70KB
  • 下载

关于C语言中函数调用和参数传递机制的探讨(二 .传递一个参数)

2.函数原型: int function(int i)     现在有了参数了,也有了返回值了,相对来说更比较复杂了。这里就得引入%esp寄存器值的变化了,不然就难以把问题分析清楚了,如果想形象...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C语言函数调用中堆栈知识
举报原因:
原因补充:

(最多只允许输入30个字)