欢迎来到个人简历网!永久域名:gerenjianli.cn (个人简历全拼+cn)
当前位置:首页 > 范文大全 > 实用文>C语言中函数参数为什么是由右往左入栈的?

C语言中函数参数为什么是由右往左入栈的?

2023-01-13 08:23:48 收藏本文 下载本文

“十三姨啊哈”通过精心收集,向本站投稿了6篇C语言中函数参数为什么是由右往左入栈的?,这次小编在这里给大家整理后的C语言中函数参数为什么是由右往左入栈的?,供大家阅读参考。

C语言中函数参数为什么是由右往左入栈的?

篇1:C语言中函数参数为什么是由右往左入栈的?

先通过一个小程序来看一看:

#includevoid foo(int x, int y, int z){printf(x = %d at [%X]n, x, &x);printf(y = %d at [%X]n, y, &y);printf(z = %d at [%X]n, z, &z);}int main(int argc, char *argv[]){foo(100, 200, 300);return 0;}

运行结果:

x = 100 at [BFE28760]

y = 200 at [BFE28764]

z = 300 at [BFE28768]

C程序栈底为高地址,栈顶为低地址,因此上面的实例可以说明函数参数入栈顺序的确是从右至左的,可到底为什么呢?查了一直些文献得知,参数入栈顺序是和具体编译器实现相关的。比如,Pascal语言中参数就是从左到右入栈的,有些语言中还可以通过修饰符进行指定,如Visual C++.即然两种方式都可以,为什么C语言要选择从右至左呢?

进一步发现,Pascal语言不支持可变长参数,而C语言支持这种特色,正是这个原因使得C语言函数参数入栈顺序为从右至左。具体原因为:C方式参数入栈顺序(从右至左)的好处就是可以动态变化参数个数。通过栈堆分析可知,自左向右的入栈方式,最前面的参数被压在栈底。除非知道参数个数,否则是无法通过栈指针的相对位移求得最左边的参数。这样就变成了左边参数的个数不确定,正好和动态参数个数的方向相反。

因此,C语言函数参数采用自右向左的入栈顺序,主要原因是为了支持可变长参数形式。换句话说,如果不支持这个特色,C语言完全和Pascal一样,采用自左向右的参数入栈方式。

这儿其实还涉及到C语言中调用约定所采用的方式,下面简单的介绍一下:

__stdcall与C调用约定(__cdecl)的区别

C调用约定在返回前,要作一次堆栈平衡,也就是参数入栈了多少字节,就要弹出来多少字节.这样很安全.

有一点需要注意:stdcall调用约定如果采用了不定参数,即VARARG的话,则和C调用约定一样,要由调用者来作堆栈平衡.

(1)_stdcall是Pascal方式清理C方式压栈,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上”@”和参数的字节数。 int f(void *p) –>>_f@4(在外部汇编语言里可以用这个名字引用这个函数)

在WIN32 API中,只有少数几个函数,如wspintf函数是采用C调用约定,其他都是stdcall

(2)C调用约定(即用__cdecl关键字说明)(The C default calling convention)按从右至左的顺序压参数入栈,由调用者把参数弹出栈,

对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数vararg的函数(如printf)只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。 _cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。

(3)__fastcall调用的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。__fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上”@”前缀,在函数名后加上”@”和参数的字节数。

(4)thiscall仅仅应用于”C++”成员函数。this指针存放于CX/ECX寄存器中,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。

(5)naked call。 当采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。

(这些代码称作 prolog and epilog code,一般,ebp,esp的保存是必须的).

但是naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。

关键字 __stdcall、__cdecl和__fastcall可以直接加在要输出的函数前。它们对应的命令行参数分别为/Gz、/Gd和/Gr。缺省状态为/Gd,即__cdecl。

要完全模仿PASCAL调用约定首先必须使用__stdcall调用约定,至于函数名修饰约定,可以通过其它方法模仿。还有一个值得一提的是WINAPI宏,Windows.h支持该宏,它可以将出函数翻译成适当的调用约定,在WIN32中,它被定义为__stdcall。使用WINAPI宏可以创建自己的APIs。

综上,其实只有PASCAL调用约定的从左到右入栈的.而且PASCAL不能使用不定参数个数,其参数个数是一定的。

简单总结一下上面的几个调用方式:

篇2:C语言中函数和指针的参数传递

最近写二叉树的数据结构实验,想用一个没有返回值的函数来创建一个树,发现这个树就是建立不起来,那么我就用这个例子讨论一下c语言中指针作为形参的函数中传递中隐藏的东西,

大家知道C++中有引用的概念,两个数据引用同一个数据,那么更改任意的一个都相当于更改了本体,那么另一个数据所对应的值也会改变,可是C中是没有这个概念的。所以就产生了一些东西。和我们本来想的有差别。

一、明确C语言中函数的入口:

C语言中函数的形参负责接收外部数据,那么数据究竟怎么进入函数的呢,其实我们在函数体内操作的形参只是传递进来参数的一个副本,也就是说这两个参数虽然名字一样,对应的值一样,但是他们两个对应的内存地址是不一样的,也就是说这就是两个“看上去一模一样”的完全不同的变量。

所以一定要知道,C语言中函数是值传递的,也就是说,C语言只能把值传给函数,而不能把你想要传递的变量完全的放进函数内部。

二、指针传递给函数:

指针作为一个特殊的东西,他的强大之处就在于指针可以直接修改内存地址上的数据。虽然指针特别强大,但是他也难逃函数的限制,你传递给函数一个指针,因为是值传递,那么你在函数体内的使用的形参指针也只是一个副本,只是一个指向的值和你传进来的那个指针一样的一个另外的一个变量。也就是说他和普通常量是没有区别的。

三、我想要达到引用的效果怎么实现

C语言中因为是值传递的,那么我们就传递值,只要讲想要传递进函数的东西的地址传进函数,并且函数用一个指针接收,那么就相当于把这个变量地址原封不动的传递给了函数,形参的指针指向的是外面传进来的地址,有了地址不就好办了吗。

四、下面是我写的一个二叉树建立的一个无返回值的版本:

因为二叉树是用递归建立的,就像建立链表一样,一个节点一个节点建立,如果不获得上一个节点的地址,你怎么把链表连接起来呢,链表就散开了。

所以只能通过传递地址来达到找到已经建好的链表的前驱,才能把各个节点穿起来。

#include#includetypedef struct tree { char t; struct tree *lchild; struct tree *rchild;}Tree;void initTree(Tree **T) { char ch; ch = getchar; if (ch == '#') { *T = NULL; } else { *T = (Tree *)malloc(sizeof(Tree)); (*T)->t = ch; initTree(&(*T)->lchild); initTree(&(*T)->rchild); }}void qianT(Tree *T) { if (T) { printf(“%c ”,T->t); qianT(T->lchild); qianT(T->rchild); }}int main (void) { Tree *T; initTree(&T); qianT(T); return 0;}

注意看树的建立那个函数,我每次都是穿进去一个节点的地址,然后通过地址来找到已经建立好的树,才能将树建立起来,

那么有的人会问了?为什么不是下面这个写法呢?

void initTree(Tree *T) { char ch; ch = getchar(); if (ch == '#') { T = NULL; } else { T = (Tree *)malloc(sizeof(Tree)); T->t = ch; initTree(T->lchild); initTree(T->rchild); }}

这种写法,你每次传进来的都是一个变量,说过,c语言是值传递的,那么每次你申请的节点空间都是给副本申请的,然后递归的是副本的左右孩子,也就是说你的树根本没有建立起来,因为每次申请的内存都没有连接上。

为什么我的那个写法可以呢,因为我是用一个指向指针的指针来存地址的,我给传进来的地址申请了内存,也就相当于给穿进来的那个节点本身申请了内存,而不是给副本,所以二叉树就顺其自然建立起来了。

最后记住,c语言是值传递的,任何东西传递给函数的都只是值!

篇3:c语言中的堆和栈的区别

堆和栈的区别

1.申请方式

(1)栈(satck):由系统自动分配,例如,声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间。

(2)堆(heap):需程序员自己申请(调用malloc,realloc,calloc),并指明大小,并由程序员进行释放。容易产生memory leak.

eg:char p;

p = (char *)malloc(sizeof(char));

但是,p本身是在栈中。

2.申请大小的限制

(1)栈:在windows下栈是向底地址扩展的数据结构,是一块连续的内存区域(它的生长方向与内存的生长方向相反)。栈的大小是固定的。如果申请的空间超过栈的剩余空间时,将提示overflow。

(2)堆:堆是高地址扩展的数据结构(它的生长方向与内存的生长方向相同),是不连续的内存区域。这是由于系统使用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由底地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。

3.系统响应:

(1)栈:只要栈的空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

(2)堆:首先应该知道操作系统有一个记录空闲内存地址的链表,但系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的free语句才能正确的释放本内存空间。另外,找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

说明:对于堆来讲,对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低,

对于栈来讲,则不会存在这个问题,

4.申请效率

(1)栈由系统自动分配,速度快。但程序员是无法控制的

(2)堆是由malloc分配的内存,一般速度比较慢,而且容易产生碎片,不过用起来最方便。

5.堆和栈中的存储内容

(1)栈:在函数调用时,第一个进栈的主函数中后的下一条语句的地址,然后是函数的各个参数,参数是从右往左入栈的,然后是函数中的局部变量。注:静态变量是不入栈的。

当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续执行。

(2)堆:一般是在堆的头部用一个字节存放堆的大小。

6.存取效率

(1)堆:char *s1=”hellow tigerjibo”;是在编译是就确定的

(2)栈:char s1[]=”hellow tigerjibo”;是在运行时赋值的;用数组比用指针速度更快一些,指针在底层汇编中需要用edx寄存器中转一下,而数组在栈上读取。

补充:

栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

7.分配方式:

(1)堆都是动态分配的,没有静态分配的堆。

(2)栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的。它的动态分配是由编译器进行释放,无需手工实现。

篇4:C语言中函数的返回值

如果某个函数从一个地方返回时有返回值,而从另一个地方返回时没有返回值,该函数并不非法,但可能是一种出问题的征兆,”

这句话觉得有些难以理解。

于是写了段测试代码,如下:

#include

int f(int i) {

if (1)

return;

else

return 1;

}

main(void) {

int i = -10;

printf(“%d\n”, f(i));

}

/*

* 本程序用gcc编译没有问题,

* 运行结果如下:

* -10

*/

篇5:C语言中调用Lua函数实例

这篇文章主要介绍了C语言中调用Lua函数实例,本文讲解了调用一个Lua函数的步骤和C语言调用Lua函数实例,需要的朋友可以参考下

记得上学时,初中英文课本中,上网叫做surfing the internet,中文叫网上冲浪,那个时期,人们经常称互联网为赛博空间。如今工作了,大量的零碎时间用于上微博,知乎,QQ,这些碎片化的阅读让人读起来轻松,也能获取些粗浅的信息。然而它们是消耗时间的黑洞,时间就这样一分一秒地飞逝,年末的时候,知乎会告诉你回答了多少问题,阅读了相当于一部《红楼梦》那么多的文字。只是当你静下来一想,这些浅阅读并没给你带来有深度,系统的知识。在你的时间线上,两条相邻信息往往是八竿子也打不着的。而且你还时不时去看看关注者有没有更新,期待让你眼前一亮的信息。结果往往是趁兴而去,败兴而回。屏幕上的信息永无止境地滚动着,是如此的热闹,仿佛每个人都在狂欢,而我的内心却如此的空虚与孤独。

在lua API中,调用一个函数的步骤很简单:

1.压入你要调用的函数,使用lua_getglobal。

2.压入调用参数。

3.使用lua_pcall

4.从栈中弹出结果。

举例说明,假设你有这么一个lua函数:

代码如下:

function f (x, y)

return (x^2 * math.sin(y))/(1 - x)

end

那么,我们就可以定义个c函数来封装这个调用:

代码如下:

/* call a function ‘f‘ defined in Lua */

double f (double x, double y)

{

double z;

lua_getglobal(L, “f”);

lua_pushnumber(L, x);

lua_pushnumber(L, y);

/* do the call (2 arguments, 1 result) */

if (lua_pcall(L, 2, 1, 0) != 0)

error(L, “error running function ‘f‘: %s”,

lua_tostring(L, -1));

if (!lua_isnumber(L, -1))

error(L, “function ‘f‘ must return a number”);

z = lua_tonumber(L, -1);

lua_pop(L, 1);

return z;

}

lua_pcall在压入结果的之前,会将函数,和参数弹出,如果返回多个结果,第一个最先压入,

如果lua_pcall运行出错,那么会返回个非0值。

(完)

篇6:c语言中字符串函数的使用

#include

#include

/*

char s1[]=“I am a student”;

char s2[20]=“teacher”;

char s3[]=“student”;

int result;

char s4[20],*p;

1.串的长度

int strlen(char *str):

printf(“%d\n”,strlen(s1));//长度为14

printf(“%d\n”,strlen(s2));//长度为7

2.复制

char *strcpy(char *str1,char *str2):

strcpy(s4,s2);//把s2复制给s4

printf(“%s\n”,s4);//输出teacher

3.比较

int strcmp(char *str1,char *str2):

result=strcmp(s2,s3);

printf(“%d\n”,result);//s2>s3

4.字符串定位

char *strchr(char *str,char ch);

p=strchr(s1,'s');//p指向在s1中字符's'的位置

printf(“%s\n”,p);//输出student

5.子串查找

char *strstr(char *s1,char *s2);

p=strstr(s1,s3);//p指向在s1中字符's'的位置

printf(“%s\n”,p);//输出student

6.连接

char * strcat(char *str1,char *str2):

strcat(s2,s3);

printf(“%s\n”,s2);//输出teacherstudent

*/

void ReverseName(char *name,char *newName){

char *p;

p=strchr(name,' ');//字符定位

*p='\0';

printf(“%s\n”,name);

printf(“%s\n”,p);

strcpy(newName,p+1);//复制

printf(“--%s\n”,newName);

strcat(newName,“,”);//连接

strcat(newName,name);//连接

*p=' ';

printf(“%s\n”,name);

}

int main(){

char name[]=“jie wang”,newName[30];

ReverseName(name,newName);

printf(“hello world\n”);

return 0;

}

【C语言中函数参数为什么是由右往左入栈的?】相关文章:

1.c语言中的1是什么意思

2.C语言中break与continue的区别

下载word文档
《C语言中函数参数为什么是由右往左入栈的?.doc》
将本文的Word文档下载到电脑,方便收藏和打印
推荐度: 评级1星 评级2星 评级3星 评级4星 评级5星
点击下载文档

文档为doc格式

C语言中函数参数为什么是由右往左入栈的?相关文章
最新推荐
猜你喜欢
  • 返回顶部