c语言程序的生命周期(编写、预处理、编译、汇编、链接、执行)
目录
- 程序的生命周期
- 一、引言
- 二、程序经历的六个阶段
- 1、阶段一:编辑(Edit)
- 2、程序编译系统(Programs Compilation System)
- 阶段二:预处理(Preprocess)
- 阶段三:编译(Compile)
- 阶段四:汇编(Assemble)
- 阶段五:链接(Link)
- 3、阶段六:执行(Execute)
程序的生命周期
一、引言
什么是计算机系统(Computer System)?
计算机系统是一起工作以运行计算机程序(Computer Program)的硬件和软件组件(hardware and software components)的集合
什么是计算机程序(Computer Program)
计算机程序是针对计算机的指令(instructions)
计算机程序是如何在计算机系统上运行的?
二、程序经历的六个阶段
- 编辑(Edit)
- 预处理(Preprocess)
- 编译(Compile)
- 汇编(Assemble)
- 链接(Link)
- 执行(Execute)
1、阶段一:编辑(Edit)
在编辑器中编写代码
如下所示:hello.c
//hello.c
#include<stdio.h>
int main(){printf("hello,world!\n");
}
2、程序编译系统(Programs Compilation System)
如图所示:
阶段二:预处理(Preprocess)
在源代码编译之前对其进行一些文本性质的操作。它的主要任务包括:
1、删除注释
2、插入被#include
指令包含的文件的内容
3、定义和替换由#define
指令定义的符号
4、确定代码的部分内容是否应该根据一些条件编译指令进行编译
条件编译简单语法形式如下:
#if constant-expressionstatements
#endif
constant-expression
:(常量表达式)由预处理器求值,如果值为非零(真),那么statements部分就被正常编译,否则预处理器就安静地删除它们。
条件语句常用于调试:例如,将你所有调试代码都以下面这种形式出现:
#if DEBUGprintf("x=%d,y=%d\n",x,y);
#endif
这样,不管我们想编译还是忽略这个代码都很容易做到。
如果想编译它,只要使用:
#define DEBUG 1
如果想忽然它,只要使用:
#define DEBUG 0
使用命令gcc -E hello.c > hello.i
生成相应的hello.i文件
文章最后会贴hello.i文件,属实有点长了
阶段三:编译(Compile)
按顺序分为三个过程:语句分析、中间代码生成、代码优化
语句分析
对整体代码进行扫描处理,对代码进行词法分析,语法分析,语义分析,以排除不合法的、不规范的代码。
词法分析:关键字的正确性、标识符的有效性、立即数的展开。
语法分析:变量命名规范、程序结构合法性、函数定义的正确性、重定义现象等
语义分析:表达式的合理性、变量未初始化使用等。
中间代码生成
中间代码生成是产生中间代码的过程。所谓“中间代码”是一种结构简单、含义明确的记号系统,这种记号系统复杂性介于源程序语言和机器语言之间,容易将它翻译成目标代码。
另外,还可以在中间代码一级进行与机器无关的优化。
使用命令gcc -fdump-tree-all hello.c
,然后查看.cfg文件,就可以看到生成的中间文件了
hello.c.013t.cfg:
;; Function main (main)main ()
{
<bb 2>:__builtin_puts (&"hello,world!"[0]);return;}
代码优化
代码优化是指对中间代码进行等价(指不改变程序的运行结果)变换
等价的含义是使得变换后的代码运行结果与变换前代码运行结果相同。
优化的含义是最终生成的目标代码短(运行时间更短、占用空间更小),时空效率优化。
使用命令gcc -S hello.c
生成hello.s文件(汇编语言)
hello.s:
.file "hello.c".def ___main; .scl 2; .type 32; .endef.section .rdata,"dr"
LC0:.ascii "hello,world!\0".text.globl _main.def _main; .scl 2; .type 32; .endef
_main:
LFB6:.cfi_startprocpushl %ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8movl %esp, %ebp.cfi_def_cfa_register 5andl $-16, %espsubl $16, %espcall ___mainmovl $LC0, (%esp)call _putsleave.cfi_restore 5.cfi_def_cfa 4, 4ret.cfi_endproc
LFE6:.def _puts; .scl 2; .type 32; .endef
编译时将变量名替换为内存地址,变量名编译完之后就不存在了
这里说的变量的内存地址是虚拟内存地址。这里的虚拟内存地址是固定的,而物理内存地址是随机的,内存使用时,才将虚拟内存映射到物理内存页
阶段四:汇编(Assemble)
针对生成的汇编代码,将其逐句解析转换生成一一对应的机器码(二进制编码)。
注意这个阶段之后才生成了二进制文件。
阶段五:链接(Link)
为什么要链接?
一个项目中有很多源文件和依赖库
源文件之间存在依赖关系
每个源文件都是独立编译的,即每个.c文件会形成一个.o文件
综上:为了实现依赖关系,需要将该.o文件中依赖的其他.o文件导入进来,这便是链接。
链接分为:静态链接和动态链接
静态链接
静态链接是以目标文件为单位的,如hello.c中,我们需要引用了静态库中的printf()函数,那么链接器就会把库中包含printf()函数的那整个目标文件都链接进来。
如果我们有几个.c文件中都用到了printf()函数
这几个.c文件都会把库中包含printf()函数的那整个目标文件都链接进来。
缺点:
那么同一份目标文件就被复制了好几份,占用的空间大。
同时,如果这个目标文件需要更新,需要重新进行编译链接形成可执行程序
优点:
优点就是,程序执行的时候已经包含了所有的函数,速度快。
动态链接
动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
操作系统负责将需要的动态库加载到内存中,然后程序在运行到指定的代码时,去共享执行内存中已经加载的动态库可执行代码,最终达到运行时连接的目的。
如果我们有几个.c文件中都用到了printf()函数
第一个.c文件会把库中包含printf()函数的那个目标文件加载到内存中。
当其他.c文件发现那个目标文件已经加载到内存中了,就不需要再加载
而是将内存中已经存在的目标文件映射到其他.c文件的虚拟地址空间中,从而进行链接
优点:
所有程序共享一份副本,文件体积小
更新比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。
缺点:
因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失
该阶段重定位时,涉及地址的改变
3、阶段六:执行(Execute)
程序如何执行?
1、hello.exe存储在硬盘中
如图所示:
2、从键盘中读取hello命令
如图所示:
3、将hello.exe从硬盘载入内存
如图所示:
4、从内存到显示器写这个输出的字符串
如图所示:
关于hello.i文件
# 1 "hello.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "hello.c"# 1 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/../../../../include/stdio.h" 1 3
# 19 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/../../../../include/stdio.h" 3
# 1 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/../../../../include/_mingw.h" 1 3
# 32 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/../../../../include/_mingw.h" 3# 33 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/../../../../include/_mingw.h" 3
# 20 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/../../../../include/stdio.h" 2 3# 1 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/include/stddef.h" 1 3 4
# 212 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/include/stddef.h" 3 4
typedef unsigned int size_t;
# 324 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/include/stddef.h" 3 4
typedef short unsigned int wchar_t;
# 353 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/include/stddef.h" 3 4
typedef short unsigned int wint_t;
# 27 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/../../../../include/stdio.h" 2 3# 1 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/include/stdarg.h" 1 3 4
# 40 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/include/stdarg.h" 3 4
typedef __builtin_va_list __gnuc_va_list;
# 29 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/../../../../include/stdio.h" 2 3
# 129 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/../../../../include/stdio.h" 3
typedef struct _iobuf
{char* _ptr;int _cnt;char* _base;int _flag;int _file;int _charbuf;int _bufsiz;char* _tmpfname;
} FILE;
# 154 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/../../../../include/stdio.h" 3
extern __attribute__ ((__dllimport__)) FILE _iob[];
# 169 "e:\\mingw\\bin\\../lib/gcc/mingw32/4.6.2/../../../../include/stdio.h" 3FILE* __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) fopen (const char*, const char*);FILE* __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) freopen (const char*, const char*, FILE*);int __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) fflush (FILE*);int __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) fclose (FILE*);int __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) remove (const char*);int __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) rename (const char*, const char*);FILE* __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) tmpfile (void);char* __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) tmpnam (char*);char* __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) _tempnam (const char*, const char*);int __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) _rmtmp(void);int __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) _unlink (const char*);char* __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) tempnam (const char*, const char*);int __attribute__((__cdecl__)) __attribute__ ((__nothrow__)) rmtmp(void);int __attribute__((__cdecl__