程序的编译和链接过程

来源:互联网 发布:lol西门代练淘宝店 编辑:程序博客网 时间:2024/05/19 16:28
一.虚拟机、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语言深度剖析》


























原创粉丝点击