一、进程所需的运行环境有哪些?
所需环境有:启动代码、环境变量、c程序的内存空间布局、库等。
(1)启动代码
(a)什么是启动代码?
故名思意就是启动程序的代码,其实所有高级语言编写的程序,都有启动代码,对于我们
C语言程序来说也是如此,也有自己的启动代码。
(b)c程序的启动代码
大家都知道,c程序都是从main函数开始运行的,不过我们平时只看到main调用别的子
函数,但是main本身作为一个函数,其实main也是需要被人调用的,被谁调用的呢?
答:被C程序的启动代码调用的。
既然启动代码如此重要,那我们就需要认真的说说启动代码,在讲启动代码时,我们会
把以下问题介绍清楚:
· 基于裸机运行的c程序 与 基于OS运行的c程序,他们在运行是有什么区别。
· 命令行的参数是如何传递给main的形参的。
· 什么是进程的正常终止 和 异常终止,return、exit、_exit这几种返回方式有什么异同
说到return、exit,其实在c++/java等中也有,它们的功能其实都是一样的。
· main函数调用return关键字时,到底发生了什么,返回的返回值到底返回给了谁,
有什么意义。
(2)环境变量表
进程在运行的过程中需要用到环境变量,而环境变量存放在了环境表中,所以我们需要讲一
讲环境变量表。
如果大家在学习java时安装过JDK的话,估计对windows的环境变量不陌生,因为安装JDK
时需要设置windows的环境变量,当然本章重点讲的是Linux的环境变量,不过为了便于理解,
会与windows的环境变量进行对比介绍。
(3)c程序的内存空间结构
c程序运行时,是运行在内存上的,也就是说需要在内存上开辟出一块空间给c程序,然后将C
代码会被从硬盘拷贝到内存空间上运行,而且这段空间必须布局为c程序运行所需的空间结构,c
程序才能运行,所以,c程序的内存空间结构也是必须要的“进程环境”。
比如程序在调用函数时需要用到“栈”这个东西,那么就必须在内存空间中构建出“栈”,否者c
序程序没办法实现函数调用。
这就好比你租了写字楼的某层开了家公司,但是这个空间肯定是需要被布局为你要的结构的
,如果空间不布局的话,你公司怎么运作起来。
所有高级语言的程序在运行时,都有自己的内存空间和空间结构,不过它们的结构都是相似的
,理解了C的内存空间布局,自然也理解其它程序的内存空间结构。
有关c程序的内存空间结构,我们在《C语言深度解析》中有详细讲过,但是由于c内存空间也
是“进程环境”之一,因此我们这里还会进行回顾。
(4)库
c程序(进程)运行时,都是需要库的支持的,至于为什么需要库,学过了c语言同学,应该都
非常清楚。
不仅c程序需要库的支持,其它高级语言的程序,同样需要库的支持,所以库也是非常重要的
进程环境,没有库的支持,我们的程序基本做不了太过复杂的事。
有关C库这一块,我们在《C语言深度解析》这门课里面有详细讲解,比如:
· 为什么需要库
· 什么是静态库,什么是动态库
· 如何制作动态库和静态库
· 等
所以在这里,不再详细讲库这个东西,在这里大家只需要理解,库是也是非常重要的进程环境。
二、本章的意义
本章的API并不多,也不复杂,当然这些API都是与进程环境相关的API,对本章来说,理解这些API是其次,
更关键的还是希望大家理解什么是进程环境。
通过对本章“进程环境”的理解,希望能够有效解决大家以前很多的疑惑,比如:
1)main函数是被谁调用的
2)main函数的返回值返回给了谁
3)main函数的参数有什么用
4)什么是进程的环境变量表
5)什么是程序的内存空间,程序的内存空间为什么要进行结构的布局
三、启动代码
3.1 启动代码的作用
在上堂课就说过,顾名思意就是用来启动整个程序的代码,所有高级语言的程序,都有自己的启动代码。
C程序运行时,最开始运行的是启动代码,启动代码再去调用main函数,然后整个C程序都已运行。
.......
|
|
子函数
|
|
子函数
|
|
main函数
|
|
启动代码
总之,高级语言程序 = 启动代码 + 自己代码。
所以,C的启动代码其实才是整个c程序的开始代码,不过由于启动代码并不是我们自己写的,所以很多
初学的同学并不知道还有启动代码这回事。
疑问:启动代码都做了些什么呢?
后面再介绍。
3.2 启动代码是由谁提供的
(1)启动代码一般都是由编译器提供的,一般有两种提供方式
1)源码形式
以源码形式提供时,编译器会将启动代码的源文件和自己程序的源文件一起编译。
编译
启动代码.c ————————————> ***.o \
\
\ 链接
————> 可执行程序
/
/
我的程序***.c ... ————> ***.o ***.o .../编译
比如我们后面学习单片机时,像开发单片机这种没有OS的计算机的C程序时,启动代码一般是源码
形式提供的,后面讲到单片机时,大家就能见到这种情况。
当然像单片机这种没有OS的裸机程序的启动代码都很简单。
2)二级制的.o(目标文件)形式
直接以.o形式提供时,省去了我自己对“启动代码”的编译。
启动代码
***.o ***.o \
\
\ 链接
————> 可执行程序
/
编译 /
我的程序 ***.c ***.c ... ——————————> ***.o ***.o .../
一般来说,如果开发的程序是运行在OS上时,那么编译器一般是以.o形式来提供启动代码,比如
我们gcc a.c时,gcc就是以.o形式提供的,基于OS运行的程序的启动代码,相对而言,自然比较复
杂些。
gcc时加一个-v选项,查看gcc编译链接的详细情况时,可以看到有很多.o,这些.o就是gcc提供
的启动代码。
(2)gcc -v
在一般情况下,不管是在命令行使用gcc命令(编译程序)方式编译还是使用IDE的图形界面方式
编译,编译链接的中间过程都被省略了,当然图形化的编译方式,最终调用的还是gcc这种编译程序
来实现的,只不过一个图形化调用的,另一个是在命令行输入命令来调用的,但是本质是一样的。
由于编译的过程被屏蔽了,因此大家对启动代码基本没有什么感觉。
演示:
gcc -v a.c
/usr/lib/gcc/x86_64-linux-gnu/5/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/
5/liblto_plugin.so
-plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper -plugin-opt=-fresoluti
on=/tmp/ccgWwfyD.res
-plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-o
pt=-pass-through=-lc
-plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --sysroo
t=/ --build-id --eh-frame-hdr
-m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x
86-64.so.2 -z relro
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/