您的位置:首页 > 编程语言 > C语言/C++

C/C++语言编译过程

2016-10-25 22:12 791 查看

程序编译

1.预处理(编译预处理,又称预编译)

把.c程序根据预处理命令组装成新的C程序,通常以.i进行命名

(1)宏定义指令,如#define name real,#undef等,对于define,预编译做的就是吧程序中的所有的name用real进行替换

(2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。这些伪指令的引入,使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序可以根据相关文件(哪些文件我也不太清楚,有知道的可以在下边留言),将不必要的代码过滤掉

(3)头文件包含指令,如
#include "FileName"
或者
#include <FileName>
等。在这些头文件中,一般包含了大量用伪指令#define定义的宏,最常见的是字符常量,同时包含有各种外部符号的声明。包含到c源程序中的头文件可以是系统提供的,这些头文件一般放在/usr/include目录下

(4)特殊符号(如#line)和预处理模块(如#pragma),这两个不太常用,不多说

2.编译、优化过程

将.i文件翻译成会变得汇编代码.s文件(在windows中,通常使用.asm便是汇编文件)

具体过程:

词法分析

语法分析

语义分析

中间代码生成

代码优化

代码生成

将多个步骤组合成趟

编译器构成工具

PS:这里解释一下”趟”的概念

这个”趟“指的是对源程序或者其等价的中间代码语言程序进行从头到尾扫视,并完成规定任务的过程。

趟数的确定:1)源语言:对于有些名字说明在使用之后的至少需要2遍 2)目标机:对于一些内存较小的机器适合多遍,因为编译程序少占的内存。

PS:说一些符号表

符号表是在编译的时候建立的,编译完成后会被保存在目标文件中,标准的elf格式可执行文件就包含了符号表, 可以用nm命令查看。举例:举例:gcc -otest.exe test.c

nm test.exe > symbal.txt

3.汇编

将汇编文件翻译成机器指令,并打包成可重定位目标程序的.o文件(在windows中,通常使用.obj表示汇编生成的二进制文件)。该文件是二进制文件,字节编码是机器指令。

.cpp代码在Linux中直接编译为.o文件,即目标文件。目标文件主要用来描述程序在运行过程中需要存放在内存中的内容。目标文件分为代码段和数据段。

代码段(.text)中的内容是源文件中定义个一个一个函数编译后得到的目标代码,包括main函数和一般的自定义函数等。数据段中包含对源文件中定义的各个静态生存期对象(包括基本类型变量)的描述。数据段又分为初始化的数据段和未初始化的数据段。这一部分对应于下文中内存分配小节中的常量区中的初始化区和未初始化区

4.链接

将引用的其他.o文件并入到我们程序所在的.o文件中,处理得到最终的可执行文件(在windows中,通常是.exe文件,缩写表示executable)。

在汇编中实现程序的连接有两种方式,一种是通过在asm中使用include命令导入其他程序,进行汇编的编译masm .asm,再进行连接link .obj,生成.exe;另一种方法是把分别汇编编译每个.asm,生成多个.obj,之后使用连接命令link a.obj,b.obj…生成.exe.(第一个汇编语言的编译器使用机器码写的)

重要概念

编译程序,使用高级语言的编译器

编译汇编程序,使用汇编语言的编译器

连接程序

翻译程序

程序的翻译通常有两种方式:编译方式解释方式,脚本语言通常使用解释的方式,如js、 python等,java被认为是一种半编译、半解释的语言(ava的编译器先将其编译为class文件,也就是字节码;然后将字节码交由jvm(java虚拟机)解释执行),关于两者的区别,自行百度.

内存分配

栈(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈

堆(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表

全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放

文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放

程序代码区—存放函数体的二进制代码

堆的分配方式:new(释放是delete),malloc(释放是free),只能动态分配

栈可以使用alloca进行动态分配,也可以静态分配(编译器完成)

栈中的内容:在函数调用时,第一个进栈的是主函数的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

堆中的内容:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

堆和栈的主要区别

1、管理方式不同;

2、空间大小不同;

3、能否产生碎片不同;

4、生长方向不同;

5、分配方式不同;

6、分配效率不同;

管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。

空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改: 打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。

注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。

碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题, 因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的 可以参考数据结构,这里我们就不再一一讨论了。

生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。

分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。

分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比 较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆 内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分 到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

new 和 malloc的区别

new 返回指定类型的指针,并且可以自动计算所需要大小。

1) int *p;   

p = new int; //返回类型为int* 类型(整数型指针),分配大小为 sizeof(int);   

或:   

int* parr;   

parr = new int [100]; //返回类型为 int* 类型(整数型指针),分配大小为 sizeof(int) * 100;   

2) 而 malloc 则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针。   

int* p;   

p = (int *) malloc (sizeof(int)*128);//分配128个(可根据实际需要替换该数值)整型存储单元,并将这128个连续的整型存储单元的首地址存储到指针变量p中

double pd=(double ) malloc (sizeof(double)*12);//分配12个double型存储单元,并将首地址存储到指针变量pd中

malloc 只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。new在分配的时候进行初始化,如果不提供显示初始化,对于类类型,用该类的默认构造函数初始化;而内置类型的对象则无初始化。

除了分配及最后释放的方法不一样以外,通过malloc或new得到指针,在其它操作上保持一致。

有了malloc/free为什么还要new/delete?

1) malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

2) 对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。

因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。

3) 既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,结果也会导致程序出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: