深入理解C语言的函数调用过程

深入理解C语言的函数调用过程
深入理解C语言的函数调用过程

深入理解C语言的函数调用过程

本文主要从进程栈空间的层面复习一下C语言中函数调用的具体过程,以加深对一些基础知识的理解。

先看一个最简单的程序:点击(此处)折叠或打开

/*test.c*/#include <stdio.h>int foo1(int m,int n,int

p){ int x = m + n + p; return x;}int main(int argc,char** argv){ int x,y,z,result; x=11;

y=22; z=33; result = foo1(x,y,z);

printf("result=%d\n",result); return 0;} 主函数main里定义了4个局部变量,然后调用同文件里的foo1()函数。4个局部变量毫无疑问都在进程的栈空间上,当进程运行起来后我们逐步了解一下main函数里是如何基于栈实现了对foo1()的调用过程,而foo1()又是怎么返回到main 函数里的。为了便于观察的粒度更细致一些,我们对test.c 生成的汇编代码进行调试。如下:点击(此处)折叠或打开.file "test.c" .text.globl foo1 .type foo1,

@functionfoo1: pushl %ebp

movl %esp, %ebp subl $16, %esp movl 12(%ebp), %eax movl 8(%ebp), %edx

leal (%edx,%eax), %eax addl 16(%ebp), %eax

movl %eax, -4(%ebp) movl -4(%ebp), %eax leave ret .size

foo1, .-foo1 .section .rodata.LC0: .string "result=%d\n" .text.globl main .type main, @functionmain: pushl %ebp

movl %esp, %ebp andl $-16, %esp subl $32, %esp movl $11, 16(%esp) movl $22, 20(%esp) movl $33, 24(%esp) movl

24(%esp), %eax movl %eax, 8(%esp)

movl 20(%esp), %eax movl %eax, 4(%esp)

movl 16(%esp), %eax movl %eax, (%esp)

call foo1 movl %eax, 28(%esp) movl

$.LC0, %eax movl 28(%esp), %edx

movl %edx, 4(%esp) movl %eax, (%esp)

call printf movl $0, %eax leave

ret .size main, .-main .ident "GCC: (GNU) 4.4.4 20100726 (Red Hat

4.4.4-13)" .section .note.GNU-stack,"",@progbits 上面的汇编源代码和最终生成的可执行程序主体结构上已

经非常类似了:[root@maple 1]# gcc -g -o test test.s

[root@maple 1]# objdump -D test >

testbin

[root@maple 1]# vi testbin

//… 省略部分不相关代码

80483c0: ff d0

call *%eax

80483c2:

c9 leave

80483c3:

c3 ret

080483c4 :

80483c4: 55

push

%ebp

80483c5:

89 e5 mov

%esp,%ebp

80483c7:

83 ec 10 sub $0x10,%esp

80483ca:

8b 45 0c mov

0xc(%ebp),%eax

80483cd:

8b 55 08 mov 0x8(%ebp),%edx

80483d0:

8d 04 02 lea (%edx,%eax,1),%eax

80483d3:

03 45 10 add 0x10(%ebp),%eax

80483d6:

89 45 fc

mov %eax,-0x4(%ebp)

80483d9:

8b 45 fc mov

-0x4(%ebp),%eax

80483dc:

c9 leave

80483dd:

c3 ret

080483de :

80483de:

55 push %ebp

80483df:

89 e5 mov

%esp,%ebp

80483e1:

83 e4 f0 and

$0xfffffff0,%esp

80483e4:

83 ec 20 sub

$0x20,%esp

80483e7:

c7 44 24 10 0b 00 00 movl $0xb,0x10(%esp) 80483ee:

00

80483ef:

c7 44 24 14 16 00 00 movl $0x16,0x14(%esp)

80483f6:

00

80483f7:

c7 44 24 18 21 00 00 movl $0x21,0x18(%esp)

80483fe:

00

80483ff:

8b 44 24 18 mov

0x18(%esp),%eax

8048403:

89 44 24 08

mov %eax,0x8(%esp)

8048407:

8b 44 24 14 mov 0x14(%esp),%eax

804840b:

89 44 24 04

mov %eax,0x4(%esp)

804840f:

8b 44 24 10 mov 0x10(%esp),%eax

8048413:

89 04 24

mov %eax,(%esp)

8048416: e8 a9 ff ff ff call 80483c4

804841b:

89 44 24 1c

mov %eax,0x1c(%esp)

804841f:

b8 04 85 04 08 mov

$0x8048504,%eax

8048424:

8b 54 24 1c mov

0x1c(%esp),%edx

8048428:

89 54 24 04

mov %edx,0x4(%esp)

804842c:

89 04 24

mov %eax,(%esp)

804842f:

e8 c0 fe ff ff call 80482f4

8048434:

b8 00 00 00 00 mov $0x0,%eax

8048439: c9

leave

804843a:

c3 ret

804843b:

90 nop

804843c:

90 nop //… 省略部分不相关代码

调用类的方法

语法如下: 语法 [访问修饰符] 返回值的类型方法名([参数列表]){ //方法体 }

(1)访问修饰符 已经讲述过类的访问修饰符,其实同理,这里的方法的访问修饰符功能也是一样,public 表示公共的,private 表示私有的。 在程序中,如果将变量或者方法声明为public,就表示其他类可以访问,如果声明为private,

(2)方法的返回类型。 方法是供别人调用的,调用后可以返回一个值,这个返回值的数据类型就是方法的返回类型,可以是int、float、double、bool、string 等。如果方法不返回任何值,就使用void。

语法 return 表达式; 如果方法没有返回值,则返回类型应该使用void(空虚;空的),用于说明无返回值。 如:public void Singing() //无返回值 { Console.Write(“在唱歌。。。”); } return 语句做两件事情:表示已经完成,现在要离开这个方法;如果方法产生一个值,这个值放置在return 后面,即<表达式>部分。意思就是“离开该方法,并且将<表达式>的值返回给调用其的程序”。

注意:在编写程序的时候,一定要注意方法声明中返回值的类型和方法体中真正的返 回的值的类型是否匹配,如果不匹配,后果很严重。比如在下面这个ToString()方法中,返 回类型是String 类型,因此在方法体中必须用return 返回一个字符串,否则编译器将报错。

(3)方法名 定义一个方法都要有一个名称 注意:方法名主要用于调用这个方法时用,命名方法就像命名变量、类一样,要遵守一定的规则,如必须以字母、下划线“_”或“$”开头,绝对不能以数字开头。

C++中函数调用时的三种参数传递方式

在C++中,参数传递的方式是“实虚结合”。 ?按值传递(pass by value) ?地址传递(pass by pointer) ?引用传递(pass by reference) 按值传递的过程为:首先计算出实参表达式的值,接着给对应的形参变量分配一个存储空间,该空间的大小等于该形参类型的,然后把以求出的实参表达式的值一一存入到形参变量分配的存储空间中,成为形参变量的初值,供被调用函数执行时使用。这种传递是把实参表达式的值传送给对应的形参变量,故称这种传递方式为“按值传递”。 使用这种方式,调用函数本省不对实参进行操作,也就是说,即使形参的值在函数中发生了变化,实参的值也完全不会受到影响,仍为调用前的值。 [cpp]view plaincopy 1./* 2. pass By value 3.*/ 4.#include https://www.360docs.net/doc/421170944.html,ing namespace std; 6.void swap(int,int); 7.int main() 8.{ 9.int a = 3, b = 4; 10. cout << "a = " << a << ", b = " 11. << b << endl; 12. swap(a,b); 13. cout << "a = " << a << ", b = " 14. << b << endl; 15.return 0; 16.} 17.void swap(int x, int y) 18.{ 19.int t = x; 20. x = y; 21. y = t; 22.}

如果在函数定义时将形参说明成指针,对这样的函数进行调用时就需要指定地址值形式的实参。这时的参数传递方式就是地址传递方式。 地址传递与按值传递的不同在于,它把实参的存储地址传送给对应的形参,从而使得形参指针和实参指针指向同一个地址。因此,被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。 [cpp]view plaincopy 1.#include https://www.360docs.net/doc/421170944.html,ing namespace std; 3.void swap(int*,int*); 4.int main() 5.{ 6.int a = 3, b = 4; 7. cout << "a = " << a << ", b = " 8. << b << endl; 9. swap(&a,&b); 10. cout << "a = " << a << ", b = " 11. << b << endl; 12. system("pause"); 13.return 0; 14.} 15.void swap(int *x,int *y) 16.{ 17.int t = *x; 18. *x = *y; 19. *y = t; 20.} 按值传递方式容易理解,但形参值的改变不能对实参产生影响。 地址传递方式虽然可以使得形参的改变对相应的实参有效,但如果在函数中反复利用指针进行间接访问,会使程序容易产生错误且难以阅读。

函数的定义和调用

函数的定义和调用 7.2函数定义 函数定义的一般形式: 类型标识符函数名(形式参数表列) 函数定义函数首部不要以分号结尾 { 说明部分 执行部分 } 例: int max(int a,int b)/*函数首部*/ ○1类型标识符○2函数名○3形式参数表列 { /*函数体开始*/○4 int z;/*说明部分*/ if(a>b)z=a; /*执行部分*/ else z=b; return(z); } 说明:函数定义包括函数首部和函数体两部分。 ○1类型标识将是指函数返回值的类型,简称函数值类型。函数的返回值由函数中的return 语句获得,即return后的表达式的值,可以是简单类型、void类型或构造类型等,注意一般函数返回什么类型的数据,函数的类型就定义成相应的类型。void类型为空类型,表示函数没有返回值。如希望不返回值,可以定义函数类型为void类型,当函数值类型为int时,可省略函数类型的说明。关于return:函数的值只能通过return语句返回主调函数,返回函数值的类型和函数定义中函数的类型应保持一致,如果函数值为int型可以省略函数类型说明,不返回函数值的函数,明确定义成空类型。 ○2函数名是函数的标识符。函数名取名遵循c语言标识符的命名规则,区分大小写。函数名后的形式参数表列给出函数的形式参数及其类型说明。 ○3形式参数简称形参,形式参数及其类型说明放在函数名后的一对圆括号中.无论函数是否有形式参数,函数名后的圆括号不可省;圆括号内没有形式参数的函数我们称之为无参函数,有形式参数的函数我们称为有参函数。强调:没有形式参数圆括号也不能省。形式参数可以是各种类型的变量,形式为:形参1类型形参1,形参2类型形参2 各参数之间用逗号间隔。在进行函数调用时,主调函数将赋予这些形式参数实际的值。 ○4函数体:函数说明之后的花括号“{}”括起来的部分,包括声明部分和执行部分: 1)声明部分:用来对函数中使用的变量和函数作说明。 2)执行部分由基本语句组成.函数的功能由函数体内的各个语句的执行来实现。 解释函数 函数的调用 一个函数被定义后,程序中的其他函数就可以使用这个函数,这个过程称为函数调用。 1。函数调用的一般形式 函数名(实参表列);实际参数表中的参数可以是常数、变量或构造类型数据,各实参之间也是用逗号分隔。对无参函数调用时无实际参数表。 函数有以下三种调用方式: (1) 函数表达式:函数调用出现在一个表达式中、这种表达式称为函数表达式。例如w =max(x,y);此时要求函数返回一个确定的值.参加表达式的计算。这里把max的返回值

MATLAB函数的调用形式

MATLAB中函数的调用形式MATLAB软件是一种可用于科技开发的高效率工具软件,它将科学计算、函数绘图与快速编程集于一体,不仅功能强大,而且易学易用,深受广大科技工作者和理工科大学生的喜爱。正在逐渐成为理工科大学生必须掌握的基本工具。 1.求函数导数的命令,调用格式是: (1)y=diff(‘f(x)’) (2)diff(‘f(x)’) (3)y=’ f(x)’ ;diff(y,’x’) (4)syms 各种变量; y=f(x);diff(y,x) 一般调用格式是: diff(y,x,n) 2.定义符号变量,一般形式: syms x y a b t 注解: syms是定义符号变量的命令, 被定义的多个变量之间用空格隔开。 3.转变一个符号表达式S的显示形式: pretty(S) 注解:pretty(S)的作用是将符号表达式S显示成更符合数学习惯的形式。 4.输入格式: fplot (‘f(x)’,[X的左界,X的右界,Y的左界,Y 的右界] 注意:●在书写运算语句时,屏幕的同一行可以同时有多个语句, 但语句之间必须用逗号或分号隔开; ●命令语句以分号结尾时,屏幕不显示运行结果; ●命令语句以逗号或不用标点结尾时,屏幕将显示运行结果。

a=100/12 %显示格式为默认的短型实数格式 format rat %显示格式转换为有理格式a format long %显示格式转换为长型实数格式 a format %还原为默认的短型实数格

5.使用clear命令可以删除所有定义过的变量, 如果只是要删除其中的某几个变量,则应在clear后面指明要删除的变量名称。 6.使用clc 命令可以清除屏幕上所有显示的内容, 但不会删除内存中的变量 7.MATLAB提供了大量的函数,可以满足各种运算需要。(1)使用命令help elfun 可列出所有的初等数学函数名。(2)使用命令help elmat可列出大量的矩阵函数名。

五种排序的算法(包括主函数调用)

#include #define MAX 100 void Quicksort(int d[],int min,int max); void Shellsort(int r[],int n); void Bubblesort(int r[],int n); void StraInsSort(int R[],int n); void Selectsort(int r[],int n); //*************************主函数********************** void main() { int s,ch,n,x,i; int a[MAX]; int p; printf("请输入待排序列中数据的个数:"); scanf("%d",&n); printf("请输入排序前序列:"); for(s=1;s<=n;s++) scanf("%d",&a[s]); { printf("0 is exit,other is continue:"); scanf("%d",&x); while(x) { for(p=1;p<=5;p++) { for(i=1;i<20;i++) printf("%c ",p);printf("\n"); printf("please input your choice(1-5):"); printf("\n1.直接插入排序\t2.希尔排序\t3.冒泡排序\n4.快速排序\t5.直接选择排序\t6.堆排序\n"); for(i=1;i<20;i++) printf("%c ",p); printf("\n"); printf("请选择:"); scanf("%d",&ch); switch(ch) { case 1: printf("\n直接插入排序\n"); StraInsSort(a,n); break;

C语言函数调用规定

在C语言中,假设我们有这样的一个函数: int function(int a,int b) 调用时只要用result = function(1,2)这样的方式就可以使用这个函数。但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。为此,计算机提供了一种被称为栈的数据结构来支持参数传递。 栈是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。 函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。 在参数传递中,有两个很重要的问题必须得到明确说明: 当参数个数多于一个时,按照什么顺序把参数压入堆栈 函数调用后,由谁来把堆栈恢复原装 在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:stdcall cdecl fastcall thiscall naked call stdcall调用约定 stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall.在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK. stdcall调用约定声明的语法为(以前文的那个函数为例): int __stdcall function(int a,int b) stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸 以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成: push 2 第二个参数入栈 push 1 第一个参数入栈 call function 调用参数,注意此时自动把cs:eip入栈 而对于函数自身,则可以翻译为: push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复 mov ebp,esp 保存堆栈指针

函数调用参数传递类型(java)的用法介绍.

函数调用参数传递类型(java)的用法介绍. java方法中传值和传引用的问题是个基本问题,但是也有很多人一时弄不清。 (一)基本数据类型:传值,方法不会改变实参的值。 public class TestFun { public static void testInt(int i){ i=5; } public static void main(String[] args) { int a=0 ; TestFun.testInt(a); System.out.println("a="+a); } } 程序执行结果:a=0 。 (二)对象类型参数:传引用,方法体内改变形参引用,不会改变实参的引用,但有可能改变实参对象的属性值。 举两个例子: (1)方法体内改变形参引用,但不会改变实参引用,实参值不变。 public class TestFun2 { public static void testStr(String str){ str="hello";//型参指向字符串“hello” } public static void main(String[] args) { String s="1" ;

TestFun2.testStr(s); System.out.println("s="+s); //实参s引用没变,值也不变 } } 执行结果打印:s=1 (2)方法体内,通过引用改变了实际参数对象的内容,注意是“内容”,引用还是不变的。 import java.util.HashMap; import java.util.Map; public class TestFun3 { public static void testMap(Map map){ map.put("key2","value2");//通过引用,改变了实参的内容 } public static void main(String[] args) { Map map = new HashMap(); map.put("key1", "value1"); new TestFun3().testMap(map); System.out.println("map size:"+map.size()); //map内容变化了 } } 执行结果,打印:map size:2 。可见在方法testMap()内改变了实参的内容。 (3)第二个例子是拿map举例的,还有经常涉及的是 StringBuffer : public class TestFun4 {

java script中的四种函数调用

《Javascript语言精粹》时,发现一些自己不知道或者没有一下子理解的东西,拿出来细细研究并记录一下。 函数被作为很重要的一部分在书中做了详细的介绍和举例。感觉函数的四种调用方式和在不同调用方式上’this’绑定的不同是个重点也是难点。欢迎大家一起研究。 方法调用模式: 调用形式:通过’.'点表达式或[subscript]下标表达式调用一个对象中的方法。this 绑定:this被绑定到被调用方法所属的对象上。例子: 帮助 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // 创建 myObject 对象。它有一个 value 属性和一个 increment 方法。 // increment 方法接收一个可选参数。如果参数不是数字,那么默认使用数字1。 var g = 'global'; var myObject = { value: 0, increment: function (inc) { document.writeln(this.g); this.value += typeof inc === 'number' ? inc : 1; // 在方法被调用时this被绑定为myObject对象。 } }; document.writeln(this.g); // global // 打印出全局变量g的值,说明this被绑定在全局对象上。 myObject.increment(); // 在这里increment方法才被调用,this才被绑定到myObject对象, 这是特有“超级”迟绑定(very late binding)。 // 这时打印出的this.g为undefined,因为myObject中没有g这个属性, 可以看出这时this被绑定到了myObject对象上。 document.writeln(myObject.value); // 1

C语言函数调用三种方式 传值调用,引用调用和传地址调

C语言函数调用三种方式传值调用,引用调用和传地址调 我想,你只要看了C语言上关于传值函数调用的测试题,一切都会了然于胸:1. 考题一:程序代码如下: void Exchg1(int x, int y) { int tmp; tmp=x; x=y; y=tmp; printf(“x=%d,y=%d\n”,x,y) } void main() { int a=4,b=6; Exchg1 (a,b) ; printf(“a=%d,b=%d\n”,a,b) } 输出的结果: x=____, y=____ a=____, b=____ 问下划线的部分应是什么,请完成。 2. 考题二:代码如下。 Exchg2(int *px, int *py) { int tmp=*px; *px=*py; *py=tmp; print(“*px=%d,*py=%d\n”,*px,*py); } main()

{ int a=4; int b=6; Exchg2(&a,&b); Print(“a=%d,b=%d\n”, a, b); } 输出的结果为: *px=____, *py=____ a=____, b=____ 问下划线的部分应是什么,请完成。 3. 考题三: Exchg2(int &x, int &y) { int tmp=x; x=y; y=tmp; print(“x=%d,y=%d\n”,x,y); } main() { int a=4; int b=6; Exchg2(a,b); Print(“a=%d,b=%d\n”, a, b); } 二.函数参数传递方式之一:值传递 1.值传递的一个错误认识 先看题一中Exchg1函数的定义: void Exchg1(int x, int y) //定义中的x,y变量被称为Exchg1函数的形式参数{

C语言函数调用规定[试题]

C语言函数调用规定[试题] 在C语言中~假设我们有这样的一个函数: int function,int a~int b, 调用时只要用result = function,1~2,这样的方式就可以使用这个函数。但是~当高级语言被编译成计算机可以识别的机器码时~有一个问题就凸现出来:在CPU中~计算机没有办法知道一个函数调用需要多少个、什么样的参数~也没有硬件可以保存这些参数。也就是说~计算机不知道怎么给这个函数传递参数~传递参数的工作必须由函数调用者和函数本身来协调。为此~计算机提供了一种被称为栈的数据结构来支持参数传递。 栈是一种先进后出的数据结构~栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项,被称为栈顶,。用户可以在栈顶上方向栈中加入数据~这个操作被称为压栈,Push,~压栈以后~栈顶自动变成新加入数据项的位置~栈顶指针也随之修改。用户也可以从堆栈中取走栈顶~称为弹出栈,pop,~弹出栈后~栈顶下的一个元素变成栈顶~栈顶指针随之修改。 函数调用时~调用者依次把参数压栈~然后调用函数~函数被调用以后~在堆栈中取得数据~并进行计算。函数计算结束以后~或者调用者、或者函数本身修改堆栈~使堆栈恢复原装。 在参数传递中~有两个很重要的问题必须得到明确说明: 当参数个数多于一个时~按照什么顺序把参数压入堆栈 函数调用后~由谁来把堆栈恢复原装 在高级语言中~通过函数调用约定来说明这两个问题。常见的调用约定有: stdcall cdecl

fastcall thiscall naked call stdcall调用约定 stdcall很多时候被称为pascal调用约定~因为pascal是早期很常见的一种 教学用计算机程序设计语言~其语法严谨~使用的函数调用约定就是stdcall.在Microsoft C++系列的C/C++编译器中~常常用PASCAL宏来声明这个调用约定~类似的宏还有WINAPI和CALLBACK. stdcall调用约定声明的语法为,以前文的那个函数为例,: int __stdcall function,int a~int b, stdcall的调用约定意味着:1,参数从右向左压入堆栈~2,函数自身修改堆栈 3,函数名自动加前导的下划线~后面紧跟一个@符号~其后紧跟着参数的尺寸以上述这个函数为例~参数b首先被压栈~然后是参数a~函数调用 function,1~2,调用处翻译成汇编语言将变成: push 2 第二个参数入栈 push 1 第一个参数入栈 call function 调用参数~注意此时自动把cs:eip入栈 而对于函数自身~则可以翻译为: push ebp 保存ebp寄存器~该寄存器将用来保存堆栈的栈顶指针~可以在函 数退出时恢复 mov ebp~esp 保存堆栈指针 mov eax~[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp~cs:eip~a~b~ebp +8指向a add eax~[ebp + 0CH] 堆栈中ebp + 12处保存了b

函数调用有哪几种方式

函数调用有哪几种方式 我们知道在进行函数调用时,有几种调用方法,主要分为C式,Pascal式.在C和C++中 C式调用是缺省的,类的成员函数缺省调用为_stdcall。二者是有区别的,下面我们用 实例说明一下:(还有thiscall和fastcall) 1. __cdecl :C和C++缺省调用方式 C 调用约定(即用__cdecl 关键字说明)按从右至左的顺序压参数入栈,由调用者把 参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数 的函数只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。 _cdecl 是C 和C++ 程序缺省的调用方式。每一个调用它的函数都包含清空堆栈的 代码,所以产生的可执行文件大小会比调用_stdcall 函数的大。函数采用从右到左的 压栈方式。VC 将函数编译后会在函数名前面加上下划线前缀。它是MFC 缺省调用约 定。 例子: void Input( int &m,int &n); 以下是相应的汇编代码: 00401068 lea eax,[ebp-8] ;取[ebp-8]地址(ebp-8),存到eax 0040106B push eax ;然后压栈 0040106C lea ecx,[ebp-4] ;取[ebp-4]地址(ebp-4),存到ecx 0040106F push ecx ;然后压栈 00401070 call @ILT+5(Input) (0040100a);然后调用Input函数 00401075 add esp,8 ;恢复栈 从以上调用Input函数的过程可以看出:在调用此函数之前,首先压栈ebp-8,然后压栈ebp-4,然后调用函数Input,最后Input函数调用结束后,利用esp+8恢复栈。由 此可见,在C语言调用中默认的函数修饰_cdecl,由主调用函数进行参数压栈并且恢复 堆栈。 下面看一下:地址ebp-8和ebp-4是什么? 在VC的VIEW下选debug windows,然后选Registers,显示寄存器变量值,然后在选debug windows下面的Memory,输入ebp-8的值和ebp-4的值(或直接输入ebp-8和-4), 看一下这两个地址实际存储的是什么值,实际上是变量n 的地址(ebp-8),m的地址(ebp-4),由此可以看出:在主调用函数中进行实参的压栈并且顺序是从右到左。另外,

分析函数调用关系图(call graph)的几种方法

分析函数调用关系图(call graph)的几种方法 绘制函数调用关系图对理解大型程序大有帮助。我想大家都有过一边读源码(并在头脑中维护一个调用栈),一边在纸上画函数调用关系,然后整理成图的经历。如果运气好一点,借助调试器的单步跟踪功能和call stack窗口,能节约一些脑力。不过如果要分析的是脚本语言的代码,那多半只好老老实实用第一种方法了。如果在读代码之前,手边就有一份调用图,岂不妙哉?下面举出我知道的几种免费的分析C/C++函数调用关系的工具。 函数调用关系图(call graph)是图(graph),而且是有向图,多半还是无环图(无圈图)——如果代码中没有直接或间接的递归的话。Graphviz是专门绘制有向图和无向图的工具,所以很多call graph分析工具都以它为后端(back end)。那么前端呢?就看各家各显神通了。 调用图的分析分析大致可分为“静态”和“动态”两种,所谓静态分析是指在不运行待分析的程序的前提下进行分析,那么动态分析自然就是记录程序实际运行时的函数调用情况了。 静态分析又有两种方法,一是分析源码,二是分析编译后的目标文件。 分析源码获得的调用图的质量取决于分析工具对编程语言的理解程度,比如能不能找出正确的C++重载函数。Doxygen是源码文档化工具,也能绘制调用图,它似乎是自己分析源码获得函数调用关系的。GNU cflow也是类似的工具,不过它似乎偏重分析流程图(flowchart)。 对编程语言的理解程度最好的当然是编译器了,所以有人想出给编译器打补丁,让它在编译时顺便记录函数调用关系。CodeViz(其灵感来自Martin Devera (Devik) 的工具)就属于此类,它(1.0.9版)给GCC 3.4.1打了个补丁。另外一个工具egypt的思路更巧妙,不用大动干戈地给编译器打补丁,而是让编译器自己dump出调用关系,然后分析分析,交给Graphviz去绘图。不过也有人另起炉灶,自己写个C语言编译器(ncc),专门分析调用图,勇气可嘉。不如要是对C++语言也这么干,成本不免太高了。分析C++的调用图,还是借助编译器比较实在。 分析目标文件听起来挺高深,其实不然,反汇编的工作交给binutils的objdump 去做,只要分析一下反汇编出来的文本文件就行了。下面是Cygwin下objdump -d a.exe的部分结果: 00401050 <_main>: 401050: 55 p

函数调用ALV方法

小总结一下函数方法调用alv的过程。其实用函数调用alv非常简单,只需要一个REUSE_ALV_GRID_DISPLAY函数即可(或者 list的方式),但是如果要做的复杂的话也可以非常复杂,如我之前的加上异常,单选框,或者加上双击命令,加上各种事件。过于复杂的不讲了,以一个平常项目中够用的例子讲解,还有其他需求直接可以再pack:slis中找到,或者se38中直接输入*demo*就可以看到一堆例子。下面是使用alv的一些必备信息。 1. 类型池(Type-pools) 如果使用函数调用ALV必须要用TYPE-POOLS: SLIS.这个类行池里包含了alv所有要用到的类型,当有哪些字段名称忘记的时候可以到这个pool里查找相应的字段。 2. 字段信息(SLIS_T_FIELDCAT_ALV) 如果我们自己写一个ALV的函数让别人调用,有哪些参数必不可缺了?字段信息肯定必不可少,字段信息决定了ALV到底输出哪些信息。通常有两种方式一是手动,二是自动。 2.1 手动生成 lt_fieldcat TYPE slis_t_fieldcat_alv. “定义字段表 WA_FIELDCAT-TABNAME = 'IT_EKKO'. WA_FIELDCAT-FIELDNAME = 'EBELN'. WA_FIELDCAT-SELTEXT_M = 'PO NO.'. APPEND WA_FIELDCAT TO I_FIELDCAT. CLEAR WA_FIELDCAT. 这样就完成了一个字段的定义,不过通常都会用一个宏来完成相应的功能。如下: DEFINE init_key. clear&1 . &1-fieldname = &2 . &1-coltext = &4. &1-outputlen = &5. &1-no_zero = 'X' . &1-key = 'X' . "冻结窗口 append &1 to &3 . end-OF-DEFINITION. 这样定义多个字段就会比较方便。

快排函数调用

int cmp(const void *a, const void *b) 返回正数就是说cmp 传入参数第一个要放在第二个后面, 负数就是传入参数第一个要放第二个前面, 如果是0, 那就无所谓谁前谁后.. 下面就把snoopy曾经写的介绍qsort的完整版贴出来好了,我想有与我一样经历的朋友也可以弄懂的: 很多人问这个东西.我以前也看了好久,今天翻到以前学快排的时候写的练习code,基本上 能覆盖绝大部分用法了. 里面有很多地方没判断相等的情况,按道理来说相等情况下应该返回0的,这个请看代码的 时候注意.我尽量保证代码不出错了. 下面的这些说明和问题都是个人原创,没查什么资料,所以不保证其完全正确性,在此表示个 人不对出现的问题负任何责任,大家WA了或者干吗的不要怪我,不过至少目前来说我用起来 是没问题的:) ** 关于快排函数的一些说明** qsort,包含在stdlib.h头文件里,函数一共四个参数,没返回值.一个典型的qsort的写法如下 qsort(s,n,sizeof(s[0]),cmp); 其中第一个参数是参与排序的数组名(或者也可以理解成开始排序的地址,因为可以写&s[i] 这样的表达式,这个问题下面有说明); 第二个参数是参与排序的元素个数; 第三个三数是 单个元素的大小,推荐使用sizeof(s[0])这样的表达式,下面也有说明:) ;第四个参数就是 很多人觉得非常困惑的比较函数啦,关于这个函数,还要说的比较麻烦... 我们来讨论cmp这个比较函数(写成cmp是我的个人喜好,你可以随便写成什么,比如qcmp什么 的).典型的cmp的定义是 int cmp(const void *a,const void *b); 返回值必须是int,两个参数的类型必须都是const void *,那个a,b是我随便写的,个人喜好.

相关文档
最新文档