编译过程

程序运行始末

1.编译器将组成程序的每一个源代码(.c)文件经过编译器complier分别编译为目标代码(Windows ->.obj;类Unix->.o),然后由链接器linker包含头文件(用户自定义头文件.h 或.hpp与引用库里的头文件.h)和所有目标代码生成一个单一的可执行文件(.exe).执行exe文件时,由操作系统将文件内容加载进内存,
预处理prepressing 编译compilation 汇编assembly 链接 linking
GCC命令选项:ESC 文件后缀:ISO
编译过程分为预处理阶段、解析源代码、生成目标代码。
一、预处理阶段\预编译: gcc -E test.c -o test.i

include头文件的包括

删除所有的#define并完成宏的替换
清除注释(替换为空格)
处理所有条件编译指令
添加行号和文件名标识
保留#pragma编译器指令给编译器使用。结束生成 .i (.ii C++)文件.
ps:防止多次编译:

1.pragma once

pragma once用来防止同一文件被多次include, #pragma once是编译相关,这里的同一文件指的是物理文件。

弊端:如果某一个头文件有多份拷贝,那么这些文件虽然在逻辑上都是一样的,但是在物理上他们却是不同的,所以把这些文件包含的时候,就会发现全部都包含进来了,然后就是编译错误了。
2.宏

define HEAD_H

endif

……(头文件内容)

ifndef HEAD_H

原理:条件编译
弊端:

ifndef最早期使用的方法,是基于语言的宏定义名字不能冲突的前提下的。如果#ifndef 后面的宏名和程序中的其他宏名”撞车“会发生报错。

不发生错误下二者区别:
当物理上的同一文件被嵌套包含的时候,使用第一种方法预处理会每一次打开该文件做判断的,但是第二种方法则不会,所以在此#pragma once 会更快些。
ANSI 标准 C 还定义了如下几个宏:
LINE 表示正在编译的文件的行号
FILE 表示正在编译的文件的名字
DATE 表示编译时刻的日期字符串,例如: “25 Dec 2007”
TIME 表示编译时刻的时间字符串,例如: “12:30:55”
STDC 判断该文件是不是定义成标准 C 程序
如果编译器不是标准的,则可能仅支持以上宏的一部分,或根本不支持。当然编译器也有可能还提供其它预定义的宏名。注意:C标准宏名的书写由标识符与两边各二条下划线构成。
二、编译: gcc -S test.i -o test.s

      将预处理完的文件进行一系列的扫描,词法分析,语法分析,语义分析,生成汇编代码及优化后产生相应的汇编代码文件。

1.词法分析:

源代码被输入到扫描器scanner 中,scanner通过类似于有限状态机的算法将源代码的字符分割为一系列的记号。
词法分析产生的记号分为关键字 标识符 字面量(数字 字符串)和特殊符号(+ - =).识别记号的同时,扫描器也将标识符存到符号表中,将数字字符串常量存放到文件表中已被后面的步骤使用。
2.语法分析

grammer parser语法分析器:完成表达式层面的分析
语法分析工具依据用户给定的语言规则构建 语法树(以表达式为节点的树)。
3.语义分析:

semantic Analyzer语义分析器
对语法树进一步完善。
4.中间语言生成:(实现跨平台)

      中间代码是编译器可以被分为前端和后端编译器前端负责产生机器无关的中间代码,编译器后端将中间代码转化为目标机器代码,这样对于一个可以跨平台的编译器而言,他们可以针对不同平台使用同一个前端和针对不同机器平台的数个后端。

5.目标代码的生成和优化:

    源代码级优化器产生中间代码标志着下面的过程都属于编译器后端,编译器后端主要包括代码生成器和目标代码优化器。 代码生成器将中间代码转换成目标机器代码,这个过程十分依赖目标机器,因为不同机器有着不同的字长,寄存器,整数数据类型和浮点数数据类型等。

经过这些扫描语法分析 语义分析 源代码优化 源代码生成和目标代码优化。。。源代码最终被编译为目标代码。但是变量的地址还没有确定,如果变量的定义与对变量的操作源代码在同一个编译单元内,编译器可以为变量分配空间。
三、汇编: gcc -O test.s -o test.o

对照汇编指令与二进制指令(机器指令)表,将汇编指令转化为二进制指令,几乎一条指令转化为一条二进制指令。结束时生成.o 目标文件
四、链接 (目标文件 库 链接)

库和头文件时两个东西。库是一组目标文件的包,即常用的代码编译成目标文件打包存放。常见的库是运行时库 runtime library CRT
1.地址和空间的分配:
编译之后的变量、函数的地址并没有确定,链接过程确定。
2.符号决议: 不同模块可能会有相同的变量名或函数名,链接要判断调用的是哪一个
3.重定位: 编译器在编译一个目标文件时,设有假一个变量的确切目标址并不知道,编译器会给他一个0.等到链接时,会将这个地址修正。

在类Unix系统中:
gcc program.c 编译并链接产生一个包含源文件的c程序。
此指令产生一个可执行的程序a.out。。中间会产生一个.o的目标文件,但在链接完成后便删除。
………….
……………
执行:
可执行文件必须加载到内存中,有操作系统的环境由操作系统加载至内存,在独立的环境中,程序的载入由手工加载,或通过可执行代码至入只读内存中。
接着程序开始执行。首先调用主函数main,
开始执行程序代码,此时程序将使用一个运行时堆栈(stack),存储函数的局部变量和地址。出此函数作用域时销毁。程序也可以使用静态内存(static),静态内存中的值保存至整个程序运行结束。
终止程序:分为正常终止main函数和意外终止。

你可能感兴趣的:(编译过程)