程序的编译和链接过程

时间:2021-10-15 00:56:21
一.虚拟机、linux简介 简单介绍一下虚拟机还有就是各种操作系统,比如centos,Ubuntu 操作系统:linux(centos、Ubuntu、redhat),Android,Windows(xp、win8、win10) 进程,多个程序,分时技术,并行技术 一次打开多个程序,我们在只有一个cpu,如何让这些程序进行执行呢,看起来好像是在同时进行的,实际上是一个程序执行了一点点时间,然后保存执行的一些信息,接下来回来再次执行,这样就达到了看上去是多个程序在同时执行的效果 操作系统的作用:控制程序的执行,管理系统的资源
二.程序的编译链接过程 程序的编译和链接过程
2.1首先看一下预处理的指令 程序的编译和链接过程

关于上面的预处理指令,大家可以自己测试一下
(测试例子)(注意是两行) ANSI 标准 C 定义的几个宏如下,大家可以简单的了解一下 __LINE__ 表示正在编译的文件的行号 __FILE__ 表示正在编译的文件的名字 __DATE__ 表示编译时刻的日期字符串,例如: "25 Dec 2007" __TIME__ 表示编译时刻的时间字符串,例如: "12:30:55" __STDC__ 判断该文件是不是定义成标准 C 程序
2.2宏定义 编译器在预处理的时候,就会把宏定义的数据替换成它的元身,这里我们必须了解什么是宏,宏的主要的用法 2.2.1 定义宏常量 #define PI 3.14159265358979323846264338327 比如我们经常使用的一个变量,如果这个变量的数值改变了之后,我们不需要再文件里面一个一个找,而是只需要把宏改变就可以了 #define ERROR_TEST -1 比如我们在test文件中,一些代码的地方需要使用return -1,或者是exit(-1) 然后有很多类型的错误返回,如果每次返回的是-1,-2,-3之类的,一会就把自己给绕晕了 2.2.2定义字符串常量 #define PATH "c/file/linux" 这个一般用在表示文件的路径的情况下有很多 (大家下去看一下,如果是一行写不下怎么办呢,就是比如一行太长了怎么办) 2.2.3 定义注释 #define ZZ // 因为在程序编译的时候,注释是先于宏处理的 2.2.4 定义表达式(宏函数) #define MUL(x) x*x 纯文本替换 测试用例 MUL(5);MUL(1+2); --- 加上括号MUL((x)*(x)) MUL(5*3)*MUL(5*3) --- 大括号???不要吝啬括号 2.2.5 空格 #define MUL (x) ((x)*(x)) 比如上面的宏函数,我们要是定义成下面的这个样子又是什么样的呢 2.2.6#undef 撤销宏定义 比如 #define PI 3.14159265358979323846264338327 ... ... ... #undef PI此时PI不在有效了 2.2.7定义宏函数补充 比如下面的程序 #define PRINTF printf("测试");
这个时候我们可以编写 一个if esle语句 if(0) PRINTF; else printf("haha\n");
宏函数比普通函数的一些特性在哪里 (1)普通的函数需要建立栈帧,开销较大,宏函数在效率上面更胜一筹 (2)函数中的参数必须使用特定的类型,换句话说,宏函数对参数是不进行类型检查的,宏函数是不类型限制的
宏函数的缺点 宏函数每次是进行的代码的替换,有时候可能会增加代码的长度,使维护起来非常的复杂 宏函数的参数不能是一个类型
2.3. 条件编译 第一种形式: #ifdef 标识符 程序段 1 #else 程序段 2 #endif 它的功能是,如果标识符已被 #define 命令定义过则对程序段 1 进行编译;否则对程序段 2 进行编译。
第二种形式 #ifndef 标识符程序段 1 #else 程序段 2 #endif
第三种形式 #if 常量表达式 程序段 1 #else 程序段 2 #endif

2.4. 文件包含 文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。C语言提供#include 命令来实现文件包含的操作,它实际是宏替换的延伸,有两种格式: #include<filename> #include"filename" 2.5.#pragma 预处理 自己上网调研,写博客,写测试用例 #pragma once #pragma warning #pragma pack--这个是张晨亮学长给你们讲过的,设置内置内存对齐数

2.6详解程序编译和链接过程 程序的编译和链接的过程 我们在linux下使用gcc编译器直接对程序进行编译,比如使用gcc test.c,然后就会生成一个程序叫做test.out 但是上面的看似简单的过程实际上是经历了四个阶段 预处理,编译,汇编,连接 每个过程都有自己要完成的任务
程序的编译和链接过程


记住一些操作,ESc对应的是iso,
2.6.1. 预处理 通过预处理生成一个.i的文件,使用的指令是 gcc -E test.c -o test.i (G) 预处理过程主要处理的是1. 以#开始的预处理指令(保留#pragma)2.删除所有的注释3.添加文件名和行号,具体的过程如下
  • 将所有的#define删除,展开所有的宏定义
  • 处理所有的条件预编译指令,比如#if,#ifdef....
  • 处理所有的#include预编译指令,将所有被包含的文件包含至本文件中来
  • 删除所有的注释,//和/* */等
  • 保留所有的#pragma预处理指令,因为下面还要用到它
2.6.2.编译 gcc -S test.i -o test.s 作用:将预处理后的文件,进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码(这里我们就可以写一个错误的语句来示范一下) 2.6.3. 汇编 gcc -c test.s -o test.o 将汇编代码解释成编译器可以执行的二进制代码 :%!xxd 2.6.4. 链接 gcc -o test.o test 这里我们有一个疑问,不是说上面的汇编已经将程序变成了二进制代码了,为什么还要链接呢,链接是咋链接呢,和谁恋呢,恋啥呢 讲述一下编写程序发展的历史,一开始是程序员是机器码,就是一个一个的纸条上面的01代码,但是这种代码太晦涩难懂了,于是出现了汇编语言,汇编语言出现之后的一个很大的问题就是,代码的数量大大的增加了,这就使得维护代码起来比较复杂,于是人们为了便于维护,又想起把代码分成不同的模块,每个模块负责不同的作用,就比如一个头文件是专门处理图片的,一个头文件是专门处理音频,这样就产生了不同的模块,这些模块之间存在着不同的调用的过程,然后就是要把不同的分开的模块组装起来就是链接的过程。 人们将每个源代码模块独立的编译,然后将编译后的模块按照他们的需求组装起来就是链接的过程。 发给大家一篇PDF《静态库和动态库》

推荐书目:《程序员的自我修养--链接、装载与库》 《C语言深度剖析》