【计算机程序如何执行】


前言


程序技术器PC的作用之计算机运行

一、计算机程序运行

1.函数讲解

就和你初中学过的函数一样,y=f(x);一个简单的函数包括两个部分,自变量和因变量。
y=f(X1,X2,X3…)而对于每一组自变量都具有唯一的因变量与其对应,这便是函数。

而程序中的函数更像一个黑箱,每当你输入一组值,它就会把另外一个经过加工处理过后的值返回给你,与初中数学的细微差别在于:c程序中参与计算的不只是数,还有字符,指针等类型…

所以我们在写函数解析式的时候需要明确四个点:函数名称,自变量的类型和个数(也叫形参列表),返回值的类型以及函数解析式(函数代码块)

int myFunction(float a,int b)
{
	代码块
	return b;
}

遵循业界标准的驼峰式命名法。返回值类型可以是基本数据类型int float double 指针…或者c++里的任何数据类型甚至无返回值void,只在函数代码块里完成打印语句等操作(或者单片机写字节等函数完成对相应的寄存器赋值等操作)

注意:返回值的类型不可以是数组。

在这里插入图片描述
像上述那样定义一个圆柱体体积计算函数,输入对应的自变量,它便将结果返回给你,你可以在程序的任何位置使用该函数。

在这里插入图片描述

由于c语言的编译从上往下的,如果将函数定义在程序入口下面的话,当编译器分析你的代码的时候,
会出现无法识别的情况,所以对于所有自定义函数,我们最好都将函数原型这一部分放在整个程序的上端,确保编译器认识他们。

#include<stdio.h>
double cylinderVolume(double r,double h);//函数声明
int main(void) {
	cylinderVolume(1, 2);
	return 0;
}

double cylinderVolume(double r, double h) {
	const double PI = 3.1415926;
	return PI * r * r * h;
}

2.程序底层实现(烧脑之旅)

严谨性:本分析省略的部分细节原理(缓冲区的建立,参数传递的规则,寄存器的压栈保存)但是会特别标出。在分析前,先弄清几个概念

程序计数器

我们所写的代码其实也保存在一定连续的内存空间中(ROM)
为了保证你写的代码能够一行行执行下去,处理器必须具有某些手段来确定下一条指令的地址。而程序计数器(寄存器)就是起到这种作用,程序计数器永远存放下一条指令的地址。(地址里面及存放的指令)为CPU指明方向

在这里插入图片描述

在这里插入图片描述
所以如果我们想要程序执行突然跳转至某个位置,只需要修改PC里面的地址值就行(目的地址即可)

通常遇到上述突然跳转有以下情况:函数调用,递归操作…而在单片机中,这些所有的操作都是在精确的时序控制下进行的,并且单片机(MCS-51)举例:PC是一个有自动+1功能的16位计数器。CPU从存储单元取指令的过程中,每取一个字节的内容,PC就自动加1,在读取完这条指令后,PC中的内容就是下一条要执行指令所在的储存单元的首地址。并且对于单片机程序计数器PC本身是没有地址的,是不可寻址的,因此用户是不能对其进行读写操作的,以上说的是CPU顺序执行指令的过程,在实际应用中,有程序的转移,子程序调用,中断响应等操作,那么这个时候PC就是根据不同的情况自动地被置入或修改新的目的地址,从而改变程序的执行顺序,此时就是不是简单的+1

运行时栈(数据结构)

栈的结构(占据连续内存空间,高地址-栈底向低地址生长-栈顶)
这样一个数据结构有什么用处呢?为什么偏要使用这种后进先出的数据结构呢?

因为除了main函数之外,还有众多其他函数:eg 程序需要从一个函数 P里跳转到另一个函数Q中,此时就应该由底层修改计数器的值,使之完成跳转,并且我们还不能一去不复返,当Q运行完时还得返回P中继续运行,于是面临一个问题:跳转至Q后,P函数中的变量和P中需要进行的下一条指令的地址需要被妥善保管,以便将来从Q中跳转回来继续运行P,同时跳转回来后Q中所有的内存都要被释放,所以栈很好的解决了这个问题。

在这里插入图片描述
函数的执行原理:在函数P中,你定义的部分局部变量都被依次压入栈中,P执行到调用Q函数时,P也会把返回地址压入栈中,指明当Q执行完后要从P的哪个位置开始执行,此时存入栈中的东西叫P的栈帧,P的栈帧只属于P函数,对其他函数不可见,暂时被挂起。

在这里插入图片描述
因此当Q函数运行时,它只需要为自己的局部变量等分配新的储存空间

在这里插入图片描述

当Q返回时,任何它所分配的局部空间都被释放(出栈)

二、举例

int Q(int x,int y){		//0xA1
	 return x+y;		//0xA2
}

int main(){		//指令地址
	int x=1;	//0x10
	int y=2;	//0x11
	Q(1,2);		//0x12
	return 0;	//0x13
}

注意:每条指令之间并不是相距1,事实上由于各种语句长度不同,有的指令会占用多个字节

首先程序先执行主函数中声明局部变量的语句,PC ->0x10,PC->0x11
计算机在栈中开辟空间,并且栈指针减小(因为向低地址生长),将x,y压入栈中,随后执行调用函数Q操作,第一步就是把紧跟着调用函数的下一条指令地址0x13压栈(返回地址),到这一步,栈指针下面的部分为main函数的栈帧

在这里插入图片描述
然后就是PC->0xA1 接着就是传递参数(x,y)所以堆栈,从右往左依次压入y,x的值到Q的栈帧中,接下来PC->0xA2,将Q栈帧中的x,y
进行加法运算将结果保存在CPU寄存器exa中,再依次将x,y出栈,此时就会取到程序的返回地址0x13,改变PC的值返回main函数中,至此完成了整个函数的调用操作。

注意

一般来说,参数的传递规则是前六个参数通过寄存器传递,后面的参数使用堆栈传递,但实际的编程中依据环境的不同会有所出入,如在函数中需要引用变量的地址,此时就不可能放在寄存器中,而本例则确实用的堆栈传递的。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值