码迷,mamicode.com
首页 > 编程语言 > 详细

C语言的灵魂(函数)

时间:2020-04-26 01:44:25      阅读:108      评论:0      收藏:0      [点我收藏+]

标签:回收   影响   bind   寄存器   最新   打开   orm   形参   硬件   

关于C语言的基础知识:常量、流程控制等等

函数的定义

  函数:一组一起执行一个任务的语句。

这里有必要说一下:通常一个较大的程序中会分为若干个模块,一个模块用来实现一个特定的功能,在大多数的编程语言中都有子程序的概念,通常都用子程序来实现模块的功能,在C语言中,子程序的作用就是由函数来完成。在程序设计中通常将一些常用的功能编写成函数,并放在函数库中供别人调用,例如C语言自带一些比较(strcmp)、拷贝(strcopy)等。

  学习C语言有两个知识点是必须要学的:

    1、函数:理解面向过程和面向对象的切入点

    2、指针:帮助我们灵活操作数据,甚至访问硬件资源

 

函数的声明

  函数声明会告诉编译器函数名称及如何调用函数,下面是函数声明的原型:

extern return_type func_name (para_list);

// 1、extern:是C语言的关键字,表明一个函数的声明,可加可不加
// 2、return_type: 返回值类型

  

函数的参数

C语言中,参数分为实参和形参

  实参:在调用是传递给函数的参数,实参可以是常量、变量、表达式、函数等。

  形参: 它不是实际存在的变量,所以又称为虚拟变量,在定义函数名和函数体时使用,目的是用来接收调用该函数时传入的参数,形参是只在被调用时才分配内存单元,结束后会被释放回收。

形参是函数被调用时用于接收实参值的变量,在调用函数时,有3种向函数传递参数的形式:

  1、传值调用:该方法把参数的实际值复制给函数的形参,这种情况修改函数内的形参不会影响实参。

  2、传地址调用:通过指针传递方式,形参为指向实参地址的指针,当对形参做指向操作时,就相当于对实参本身进行操作。

  3、传引用调用:引用“&”就是别名(相当于每个人都有大名和小名,这里区别于go语言,取址符号),所以程序对引用作出改动,其实就是对目标的改动。

实例:

#include <stdio.h>

void swap (int a, int b) {  // 传值交换
    int temp;
    temp = a;
    a = b;
    b = temp;
}

void swap2(int *a, int *b) {// 传地址交换, 
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

/* 特此说明C语言不支持这种引用传递,C++可以
void swap5(int &a,int &b) {
    int temp;
    temp = a;
    a = b;
    b = temp;
}

*/

int main() {
    int a=1, b=2;  // 值传递
    swap(a,b);
    printf("a=%d,b=%d\n",a,b);

    int c=10,d=20;
    swap2(&c, &d);
    printf("a=%d,b=%d\n",c,d); //

    // int e=100, f=200;
    // swap5(e,f);
}

  

函数的调用过程

首先完成一个简单的C程序:

#include <stdio.h>
int plus(int a, int b) {
    int c = a + b;
    return c;
}

int main() {
    int a =1,b=2;
    int c=0;
    c = plus(a,b);
    printf("%d",c);
    return 0;
}

  使用:gcc -o demo demo.c;来生成可执行文件demo,再通过命令:objdump -d demo > demo.txt,得到该可执行程序的反汇编指令,如下

生成的汇编文件分析:

demo:	file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
__text:
100000f20:	55 	pushq	%rbp   // 在linux C中所有的main函数都是被__libc_start_main调用,这里把rbp的地址压栈,每次压栈后,rsp都是指向最新的栈顶
100000f21:	48 89 e5 	movq	%rsp, %rbp   // rsp要么在内存空间中处于一个段的低地址,要么和rbp重合,这里rbp也指向栈顶,重一起表示函数的栈底地址
100000f24:	89 7d fc 	movl	%edi, -4(%rbp)
100000f27:	89 75 f8 	movl	%esi, -8(%rbp)
100000f2a:	8b 75 fc 	movl	-4(%rbp), %esi
100000f2d:	03 75 f8 	addl	-8(%rbp), %esi
100000f30:	89 75 f4 	movl	%esi, -12(%rbp)
100000f33:	8b 45 f4 	movl	-12(%rbp), %eax
100000f36:	5d 	popq	%rbp
100000f37:	c3 	retq
100000f38:	0f 1f 84 00 00 00 00 00 	nopl	(%rax,%rax)
100000f40:	55 	pushq	%rbp
100000f41:	48 89 e5 	movq	%rsp, %rbp
100000f44:	48 83 ec 20 	subq	$32, %rsp
100000f48:	c7 45 fc 00 00 00 00 	movl	$0, -4(%rbp)
100000f4f:	c7 45 f8 01 00 00 00 	movl	$1, -8(%rbp)
100000f56:	c7 45 f4 02 00 00 00 	movl	$2, -12(%rbp)
100000f5d:	c7 45 f0 00 00 00 00 	movl	$0, -16(%rbp)
100000f64:	8b 7d f8 	movl	-8(%rbp), %edi
100000f67:	8b 75 f4 	movl	-12(%rbp), %esi
100000f6a:	e8 b1 ff ff ff 	callq	-79 <_plus>
100000f6f:	89 45 f0 	movl	%eax, -16(%rbp)
100000f72:	8b 75 f0 	movl	-16(%rbp), %esi
100000f75:	48 8d 3d 36 00 00 00 	leaq	54(%rip), %rdi
100000f7c:	b0 00 	movb	$0, %al
100000f7e:	e8 0d 00 00 00 	callq	13 <dyld_stub_binder+0x100000f90>
100000f83:	31 f6 	xorl	%esi, %esi
100000f85:	89 45 ec 	movl	%eax, -20(%rbp)
100000f88:	89 f0 	movl	%esi, %eax
100000f8a:	48 83 c4 20 	addq	$32, %rsp
100000f8e:	5d 	popq	%rbp
100000f8f:	c3 	retq     // 以上都是完成_libc_start_main函数的汇编

_plus:    // plus函数的汇编部分
100000f20:	55 	pushq	%rbp  // 将rbp的地址压栈
100000f21:	48 89 e5 	movq	%rsp, %rbp  // 将rsp和rbp重叠表示整个函数的栈底
100000f24:	89 7d fc 	movl	%edi, -4(%rbp)
100000f27:	89 75 f8 	movl	%esi, -8(%rbp)
100000f2a:	8b 75 fc 	movl	-4(%rbp), %esi
100000f2d:	03 75 f8 	addl	-8(%rbp), %esi
100000f30:	89 75 f4 	movl	%esi, -12(%rbp)
100000f33:	8b 45 f4 	movl	-12(%rbp), %eax
100000f36:	5d 	popq	%rbp
100000f37:	c3 	retq
100000f38:	0f 1f 84 00 00 00 00 00 	nopl	(%rax,%rax)

_main:  // main函数部分汇编
100000f40:	55 	pushq	%rbp  
100000f41:	48 89 e5 	movq	%rsp, %rbp  // 初始化压栈和函数栈顶
100000f44:	48 83 ec 20 	subq	$32, %rsp  // 为main函数开辟空间
100000f48:	c7 45 fc 00 00 00 00 	movl	$0, -4(%rbp)   // 将变量1压栈(a)
100000f4f:	c7 45 f8 01 00 00 00 	movl	$1, -8(%rbp)   // 将变量2压栈(b)
100000f56:	c7 45 f4 02 00 00 00 	movl	$2, -12(%rbp)  // 将变量3压栈(c)
100000f5d:	c7 45 f0 00 00 00 00 	movl	$0, -16(%rbp)  
100000f64:	8b 7d f8 	movl	-8(%rbp), %edi   // 设置寄存器edi保存实参
100000f67:	8b 75 f4 	movl	-12(%rbp), %esi  // 设置寄存器esi保存实参
100000f6a:	e8 b1 ff ff ff 	callq	-79 <_plus>
100000f6f:	89 45 f0 	movl	%eax, -16(%rbp)  // 形参入栈
100000f72:	8b 75 f0 	movl	-16(%rbp), %esi
100000f75:	48 8d 3d 36 00 00 00 	leaq	54(%rip), %rdi
100000f7c:	b0 00 	movb	$0, %al
100000f7e:	e8 0d 00 00 00 	callq	13 <dyld_stub_binder+0x100000f90>   // 调用plus函数
100000f83:	31 f6 	xorl	%esi, %esi
100000f85:	89 45 ec 	movl	%eax, -20(%rbp)
100000f88:	89 f0 	movl	%esi, %eax
100000f8a:	48 83 c4 20 	addq	$32, %rsp
100000f8e:	5d 	popq	%rbp
100000f8f:	c3 	retq
Disassembly of section __TEXT,__stubs:
__stubs:
100000f90:	ff 25 6a 10 00 00 	jmpq	*4202(%rip)
Disassembly of section __TEXT,__stub_helper:
__stub_helper:
100000f98:	4c 8d 1d 69 10 00 00 	leaq	4201(%rip), %r11
100000f9f:	41 53 	pushq	%r11
100000fa1:	ff 25 59 00 00 00 	jmpq	*89(%rip)
100000fa7:	90 	nop
100000fa8:	68 00 00 00 00 	pushq	$0
100000fad:	e9 e6 ff ff ff 	jmp	-26 <__stub_helper>

  

函数递归

我们以最常见的斐波拉契数列为例:

#include <stdio.h>

long Fibonacci (int n) {
    if (n < 0){
        return -1;
    }else if(0==n){
        return 0;
    }else if(1==n){
        return 1;
    }else{
        return Fibonacci(n-1) + Fibonacci(n-2);
    }
}

int main() {
    int* n;
    printf("请输入n的值:");
    scanf("%d",n);
    printf("第n项的值为:%ld\n",Fibonacci(*n));
    return 0;
}

  小编第一次跑这个程序的时候输入了70,发现半天没有响应,打开性能统计cpu占了99%,后来发现在40的时候y都已经上千万了...

 

可变参数列表

当我们无法列出传递函数的所有实参类型和数目时,我们可以使用省略号参数表例如:

int  printf(const char* format ...);
// 这就是printf的函数原型

  我们之前介绍过参数是以数据结构栈的方式进行存取的,从右到左入栈,如之间将的压栈图,如之前我们调用的额函数(先b参数入栈,再a参数入栈);理论上来说我们只要知道一个参数的地址,那么其他的变量地址都可以进行捕捉到,这里我们讲解哈如果实现可变参数的功能:因为参数是从右到左压栈,当我们不知道参数个数的时候,应该如何开辟栈?通过<stdarg.h>中定义的宏来解决:

typedef  char* va_list  // 该变量用于存储参数
void va_start (....)   // 当前参数靠右边的一个参数
type va_arg (...)  // 获取到参数
void va_end (...)  // 左后一个参数

  代码实例:

#include <stdio.h>
#include <stdarg.h>

int average(int n,...) {
    va_list arg; // 返回一个参数列表
    int i = 0;
    int sum = 0;
    va_start(arg,n); // 制定起始值
    for (i=0;i<n;i++){
        sum += va_arg(arg,int); // 制定参数类型返回该参数
    }

    va_end(arg); // 结束
    return sum/n;
}

int main() {
    int a = 10;
    int b = 20;
    int c = 30;

    int avg1 = average(2,a,b);
    int avg2 = average(3,a,b,c);
    printf("avg1=%d\n",avg1);
    printf("avg2=%d\n",avg2);
}

  

C语言的灵魂(函数)

标签:回收   影响   bind   寄存器   最新   打开   orm   形参   硬件   

原文地址:https://www.cnblogs.com/double-W/p/12775468.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!