一、c/c++程序编译过程
C语言的编译连接过程把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行文件:win下是.obj,linux下是.so),通过编译和链接两个步骤实现。
为了我们编写的hello.c程序可以被执行,驱动硬件电路工作,hello.c程序必须经过一些列处理步骤,将源程序转化为可执行性的目标程序。
编译:把文本形式源代码翻译成机器语言形式的目标文件的过程
链接:把目标文件、操作系统的启动代码和用到的库文件进行组织,最终生成可执行文件代码的过程。
源程序到目标程序执行(GCC编译源代码)有四个步骤:预处理-》编译-》汇编-》链接
1.预处理(.c—.i)处理宏
预处理的过程主要处理包括以下过程:
- 将所有的#define删除,并且展开所有的宏定义
- 处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif等
- 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。
- 删除所有注释 “//”和”/* */”.
- 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
- 保留所有的#pragma编译器指令,(1)设定编译器状态,(2)指示编译器完成一些特定的动作。
命令:
gcc -E hello.c -o hello.i //可以用gcc的参数-E来参看。参数-E表示只进行预处理 或者也可以使用以下指令完成预处理过程
cpp hello.c > hello.i /* 直接cat hello.i 你就可以看到预处理后的代码 */
2.编译(.i—.s)转换为汇编语言文件
这个阶段编译器主要做词法分析、语法分析、语义分析等,在检查无错误后,把代码翻译成汇编语言。可用gcc的参数-S来参看。
编译器(ccl)将文本文件hello.i 翻译成文本文件hello.s, 它包含一个汇编语言程序。 一条低级机器语言指令。
命令:gcc -S hello.i -o hello.s
作用:将预处理输出文件hello.i汇编成hello.s文件
3.汇编(.s—.o)得到机器语言
汇编器将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。汇编相对于编译过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。
汇编器as 将hello.s 翻译成机器语言保存在hello.o 中(二进制文本形式)。
命令:gcc –c hello.c –o hello.o或者$ as hello.s –o hello.o
由于hello.o的内容为机器码,不能以普通文本形式的查看(vi 打开看到的是乱码)。
4.链接
通过调用链接器ld来链接程序运行需要的一大堆目标文件,以及所依赖的其它库文件,最后生成可执行文件。就是将二进制文件链接成为一个可执行的指令。
链接主要内容:把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确地衔接。
链接主要过程:地址和空间分配,符号决议,重定位等。
链接分为静态链接和动态链接。
静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行文件会比较大。
动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。
gcc生成动态库、静态库:https://www.jb51.net/article/34990.htm
5.运行
平常用下面的命令行直接运行:
gcc -c hello.c
得到 hello.o 文件。.o是object文件的意思。不可以执行。
用-o编译链接一下:
gcc -o hello hello.c
./hello
成功运行,最后使用 ldd 命令查看hello依赖的动态库。
汇编和反汇编的区别
汇编:将汇编语言翻译成机器语言的过程。 也就是hello.s文件经过汇编器变成二进制目标文件hello.o文件的过程。
反汇编:由已生成的机器语言(二进制语言)转化为汇编语言的过程,也就是汇编的逆向过程。
在Linux下利用反汇编器对.o文件进行反汇编,得到的内容和hello.s是一致的
命令: objdump -d hello.o
二、java程序编译过程
java文件首先被编译成字节码,jvm运行字节码文件,java程序执行包括了编译和运行两部分。
编译:在编译一个类的时候,如果其依赖的一个类还没有被编译,被依赖的类就会先被编译,然后引用,否则直接引用,当找不到被依赖类的java或class文件时,就会报cant find symbol错误。class文件中包含常量池和字节码两部分,常量池包含了一些类信息,常量信息、符号引用,字节码包含的是各个方法的字节码
运行:运行包括加载和执行两部分,jvm并不会把所有的字节码文件都一次加载进内存中,而是当第一次用到的时候才加载,并且只加载一次。
当类被加载时,会在堆中创建该对象,同时会在方法区中存储该类的静态变量(类变量、类方法等),并生成映射关联堆中的对象与其方法区中存储的数据。
假设现在有A.class和B.class,在A的main函数中创建了B对象且调用了B的b()方法:
类的加载时机:当这个类被实例化的时候如:new A();或者执行这个类里面的static(静态方法时)如:main方法,这个类才会被加载
(1)执行java A命令,启动一个jvm进程,找到A的字节码文件,进行加载,将A的类信息存储在方法区中
(2)找到A的main函数入口,发现要new B对象,加载B的字节码文件,将类信息存储在方法区中,并在堆中创建B对象
(3)在调用B的b()方法时,首先jvm根据引用找到找到B对象的在堆中的位置,然后根据映射找到方法区中,B的方法表,获取b()方法的字节码地址
(4)运行b()方法