首页 /  技术专区  /  C 屏幕太窄?试试伸展一下吧 >

程序的加载、运行(1)

1 程序的加载、运行

编译得到可执行目标文件后,就可以将“可执行目标文件”加载“运行地址”所指的内存位置,然后运行了。

不过这里还是要分两种情况来看,第一种是裸机运行的情况,第二种是基于OS虚拟内存运行的情况。

(1)裸机的情况

        使用专门针对裸机的编译器来编译程序,最后得到的就是可以在裸机上运行的可执行程序。

        加载裸机程序时,由专门的加载程序(加载软件)来实现的。

        如果你无法想象裸机程序是如何加载的话,你就想一想单片机程序的加载,因为单片机其实就是裸机的情况。

1)加载

其实加载的过程就是将“代码段”和“数据段”复制到内存上

裸机时,链接器重定位后的“运行地址”是真实的物理地址,加载时直接将“代码段”和“数据段”复制到物理内存中“运行地址”所指定的位置。

裸机运行地址是多少,可以由我们程序员自己来定。

image.png

注意:裸机时就不是ELF格式头了,而是bin格式头。

2)运行         

(a)CPU的PC(程序计数器)存放第一条指令_start的地址,也就是将PC指向第一条指令_start。

pc是cpu的寄存器之一。

(b)从_start开始执行启动代码。

(c)启动代码调用_init等函数进行初始化。

        初始化有一件非常重要的事情就是,从内存划出一片空间出来用作堆和栈,因为空间是以堆和栈的方式来管理的,因此就称为堆 和 栈。

(d)启动代码调用main函数,main函数再调用各个子函数,我们自己写的代码就开始运行了。

(e)main函数调用return关键字,返回到启动代码。

        对于裸机的来说,返回到启动代码就结束了。

        至于return的返回值,有没有返回值,对于裸机来说都没有什么影响。

        就算有返回值,将返回值返回给启动代码后,这个返回值对启动代码来说也没有什么意义。

        所以说,对于裸机来说,其实main函数的返回值没有什么意义,所以大家在学习单片机时候,以前的main函数的返回值都是void的。

void main(void)
{
    return;
}

不过现在都规范化了,单片机等裸机里面,也要求main函数的返回值类型为int型。

int main(void)
{
    return 0;
}

尽管在这里要求返回int型的返回值,但是我们自己应该清楚,在裸机下,main函数的返回值并没有什么大的意义。

3)栈、堆

前面说过,程序运行起来后,初始化代码会从内存中划出一片空间,用来作为程序运行所需要的栈和堆。

image.png

(a)栈(stack)

栈的意思是,表示内存空间以栈这种数据结构来进行管理,所谓管理就是管理空间的开辟和释放。

学过栈这种数据结构的同学都知道,栈的特点是,只能在栈顶进行操作,不能够在栈的中间和栈底操作。

· 栈是向下生长的

所谓向下生长就是,栈底在最高地址处,当栈中没有任何空间被使用时,栈顶指针就指向栈底,每当栈顶被占用一个字节的空间,栈顶指针就向低地址方向移动一个字节。

从高地址向低地址方向移动,就是向下生长,栈顶指针所指的那个字节是没被用的。

栈顶和栈底之间的栈空间,就是被占用的空间。

反过来,栈顶指针向高地址后退一个字节,就表示释放一个字节的空间。

释放的意思就是将空间交出去,让别人可以使用。

怎么理解栈顶指针?

就是某个寄存器或则指针变量,专门用于存放栈顶字节的地址。

· 栈的作用

函数自动局部变量、形参等就开辟于栈。

int fun(int a)
{
    int b;
    ...
}

不过这里有一点需要强调下,对于ARM来说,由于arm cpu内部寄存器比较多,所以如果形参在4个以内的,实际上形参是在寄存器中,而并不在栈中。

如果超过4个的话,第4个往后的形参才会存在栈中。

不过在intel的CPU上又不一样,因为Intel cpu的寄存器比较紧俏,所以形参基本都是存在栈中的。

我们这里为了讲课的方便,我们一律认为形参都是在栈中的。

从栈中开辟和释放自动局部变量、形参空间的过程,由函数被调用时,在运行的过程中自动完成的,

无需程序员关心,开辟空间和释放空间的本质,其实就是栈顶指针移动的过程。


(b)堆(heap)

堆空间和栈空间的管理方式是有区别的。

        · 栈的话只能在栈顶才能进行操作,但是堆不是,堆的话可以在中间任何位置操作。

        · 堆的空间是向上生长的,也就是说在堆中开辟空间时与栈相反,是从低地址往高地址方向延伸的。

        · 栈的空间是自动开辟和释放的,但是堆的空间不是的,堆只能手动开辟和释放。

- 从堆里面开辟空间

        程序需要调用malloc函数来手动开辟。

        所谓手动开辟,就是程序员需要在程序中亲自调用某个函数来实现,至于说在堆中什么位置开辟空间,这个由malloc函数的算法来决定。

- 释放在堆中开辟的空间:在程序中调用free函数,手动释放

        释放的意思,也是将空间让出来,让别人可以使用。



0/200
图片验证码