C/C++的编译与运行
C/C++编译前,首先要对源代码执行预处理。预处理器(preprocessor)是一个简单的程序,它用程序员(利用预处理器指令)定义好的模式代替源代码中的模式(删除注释、包含其他文件以及执行宏),预处理后生成中间文件.i(文本)。接下来对于.i文件进行语法分析。编译器把源代码分解成小的单元并把它们按树形结构组织起来。表达式中“A + B”中的“A”、“+”和“B”就是语法分析树的叶子节点。语法分析树建立后有时会根据用户定义,使用全局优化器(global optimizer)来生成更短、更快的代码。
全局优化器主要是进行以下优化:
局部和全局公共子表达式消除
在此优化中,计算一次公共子表达式的值。在下面的示例中,如果 b 和c 的值在三个表达式之间没有更改,则编译器可以将 b+c 的计算分配给一个临时变量,并用此变量替代b + c:
i = -100;
t = x + y;
while( i < 0 )
{
i += t;
}
当编译器不能假定任何别名时(通过 __restrict、noalias 或 restrict 设置),循环优化更有效。
提示:
在VS的编译中使用带 g 选项的 optimize 杂注,可以逐个函数地启用或禁用全局优化。
优代完成之后,还需要使用代码生成器(code generator)遍历语法分析树,把树的每个节点转化成汇编语言,这个期间保存为中间文件.s(汇编语言 文本)。之后根据用户定义可以使用窥孔优化器(peephole optimizer)从相邻一段代码中查找冗余汇编语句。
窥孔优化,顾名思义,是一种很局部的优化方式,编译器仅仅在一个基本块或者多个基本块中,针对已经生成的代码,结合CPU自己指令的特点,通过一些认为可能带来性能提升的转换规则,或者通过整体的分 析,通过指令转换,提升代码性能。别看这些代码转换很局部,很小,但可能会带来很大的性能提升。这个窥孔,你可以认为是一个滑动窗口,编译器在实施窥孔优化时,就仅仅分析这个窗口内的指令。每次转换之后, 可能还会暴露相邻窗口之间的某些优化机会,所以可以多次调用窥孔优化,尽可能提升性能。窥孔优化可以在四个方面寻找优化机会:冗余指令删除,包括冗余的load和store指令以及死代码(不会执行的代码);控制流优 化;强度削弱;利用特有指令。
删除冗余load和store,比如这段汇编代码:sh $0,6($sp)
sh $0, 4($sp)
sh $0, 2($sp)
sh $0, 0($sp)
ldc1 $f1, 0($sp)
完全可以使用指令xor $f1,$f1,$f1
这样可以省掉5次访存操作,性能的提升非常明显。上面sh是store 16bit到某个地址,ldc1是load 64bit到某个寄存器。xor是异或指令。接下来使用汇编器将汇编源文件翻译成对应的机器指令,而且还写入一些东西与机器指令打包成可重新定位目标程序格式的文件,生成中间文件.o(目标文件 二进制)。最后使用连接器(linker)把一组目标模块连接成为一个可执行程序(最终文件),简单的讲,把目标的库文件和所需要的引用的静、动态链接库进行链接,即需要把其他静态库合成到可执行文件中,转换相应的符号引用为地址,然后确保所引用的其他动态链接库的符号存在。此外,链接器还要完成程序中各目标文件的地址空间的组织,这可能设计重定位工作。大多数现代操作系统都提供静态链接和动态链接两种形式。
注:静态类型检查是在建立语法分析树时完成的。
windows程序的启动过程
命令解释器(shell)调用了CreateProcess系统函数,创建一个“进程内核对象”。进程内核对象可以看作一个操作系统用来管理进程的内核对象,它也是系统用来存放关于进程统计信息的地方(一个小的数据结构),其实它的真正创建者是一个叫NtCreateProcess的windows2000系统服务函数(也叫执行体服务函数),他创建了进程内核对象供用户扩展。进程内核对象的初始使用计数为1。然后系统为该进程创建4GB(=2^32)的虚拟地址空间(所谓虚拟就不是真的创建4GB的物理内存空间,这些空间不是真在物理内存上)。用于加载App.exe可执行文件和任何必要的dll文件的数据和代码。4G的虚拟内存中,用户进程可以占有2GB的私有地址空间;操作系统占有剩余的2GB空间。在32位x86系统中:从0x00000000到0x7fffffff的空间中存放着 应用程序代码,全局变量,每个线程堆栈,dll代码。
从0x80000000到0xc0000000的空间中存放着 内核和执行体,HAL(硬件抽象层),引导驱动程序。
从0xc0000000到0xc0800000的空间中存放着 进程页表和超空间。
从0xc0800000到0xffffffff的空间中存放着 系统高速缓存,分页缓冲池,非分页缓冲池。
CreateProcess打开应用程序文件,它先扫描该文件的文件头,该文件头里含有文件能运行在哪个环境之下,如果是win32环境,系统就直接加载文件的代码和数据并输入(import)该文件执行所需的动态链接库。如果不是win32环境比如时os/2的.exe则先加载相应的环境子系统,载由该环境加载该文件的代码和数据以及该文件执行所需的动态链接库。具体加载动态链接库过程如下加载器(loader)读入可执行程序的导入符号表,根据这些符号表可以查找出该可执行程序的所有依赖的动态链接库。加载器针对该程序的每一个动态链接库调用LoadLibrary(1)查找对应的动态库文件,加载器为该动态链接库确定一个合适的基地址。如果该基地址和动态链接库希望记载的基地址不同,加载器还要为该库做rebase,然后把整个动态链接库映射到进程的虚拟内存空间中。
(2)加载器读取该动态链接库的导入符号表和导出符号表,比较应用程序要求的导入符号是否匹配该库的导出符号。
(3)针对该库的导入符号表,查找对应的依赖的动态链接库,如有跳转,则跳到第三步
(4)调用该动态链接库的初始化函数
进程加载代码和数据完毕后,就开始创建线程来执行进程空间内的代码。进程是静态的,它只是线程的容器。一个进程至少因该有一个线程(main thread),其它线程都是主线程通过调用CreateThread函数创建的。线程也是核心对象,他的实际创建者是一个叫NtCreateThread的windowsNT系统服务函数。一个线程其实只是一个线程核心对象和两个堆栈(一个核需要ANSI字符和字符串的GUI应用程序的启动函数是WinMainCRTStartup,其对应的进入点函数是WinMain
需要Unicode字符和字符串的GUI应用程序的启动函数是wWinMainCRTStartup,其对应的进入点函数是wWinMain
需要ANSI字符和字符串的CUI应用程序(如控制台console程序)的应用程序的启动函数是mainCRTStartup,对应的入口点函数为main
需要Unicode字符和字符串的CUI应用程序(如控制台console程序)的应用程序的启动函数为wmainCRTStartup,对应的入口点函数为wmain
c/c++运行时库的启动函数的功能如下(以wWinMainCRTStartup为例):Linux程序启动过程
输入命令,回车exec系统调用接管,为应用程序的运行准备一些环境便利爱那个等,并且为运行的命令找到相应的解释器。通常应用程序解释器就是ld,ld接管控制权后先需要读取这个可执行程序的文件的一部分,包括文件头及共享对象(so文件)针对每一个依赖的库,ld需要首先读入这个so的一部分文件头和相关信息,然后递归查找该共享对象所依赖的其它共享对象,直到最底层。ld会把所有依赖的so映射到该程序进程空间的虚拟内存中(注意是 映射不是读入),由于,每一个共享对象在该进程的虚拟内存空间中占据不同的连续区域,他们的“基地址各不相同”,从而其内部的一些用绝对地址表示的符号需要做出相应的修改初始化应用程序的全局变量,对于全局对象子哦的那个调用构造函数从入口函数开始执行本文转自 http://www.cnblogs.com/eVCrow/articles/1870961.html