一.编译过程
1.概述
预处理:包括预编译、编译、汇编、链接
-
1.预编译(不进行语法检查)
将
.c
中的头文件展开、宏展开生成.i
文件 -
2.编译
将预处理之后的
.i
文件生成.s
汇编文件 -
3.汇编
将
.s
汇编文件生成.o
目标文件 -
4.链接
将
.o
文件链接成目标文件
2.Linux下GCC编译器编译过程
(1) 单文件程序编译
**假设:**要编译的时hello.c
这个C语言程序
1.预编译:
gcc -E hello.c -o hello.i
2.编译:
gcc -S hello.i -o hello.s
3.汇编:
gcc -c hello.s -o hello.o
4.链接
目标名可以随便起,不需要后缀
gcc hello -o hello_elf
(2)多文件程序编译
注意:多文件链接必须有包含main方法的文件,否则程序会因为找不到主入口二无法运行。
① 概述
1.对所有.c
文件进行预处理:
先对所有的.c
文件进行预编译、编译、汇编、链接操作,得到所有程序文件的.o
文件
2.链接阶段,链接所有.o
文件:
在链接阶段,在输出那里填入所有涉及的程序的.o
文件,然后进行链接即可
② 概述
例如要链接main.c、hello.c、test.c这3个c文件,对应命令如下(省略了编译的命令)
gcc main.o hello.o test.o -o test
二.相关指令
1.#include
(1)概述
#include
指令用于告诉编译器在实际编译之前要包含标准输入输出头文件,
编译器会在预编译的时候将#include
指令所处位置替换成头文件的内容
(2)参数
注意:
- include虽然可以包
.c
文件,但不要包,容易导致函数重复定义 - 该指令后面不要加分号
;
用**尖括号<>**包裹头文件:编译器找头文件时,只会去系统指定路径找,不会在本地找
用**双引号""**包裹头文件:编译器会先在当前目录找头文件,找不到才回去系统指定路径找
(3)示例
头文件: max.h
int max(int x,int y);
调用文件: main.c
#include "max.h"
int main(){
int num;
num = max(10,20);
retrun 0;
}
预处理结果:
将#include
指令所处位置替换成头文件的内容
int max(int x,int y);
int main(){
int num;
num = max(10,20);
retrun 0;
}
2.#define
(1)概述
用来进行宏定义的指令,使用宏定义的位置会在预编译时期替换成宏所指代的内容
(2)分类
① 不带参宏
在预编译后,所有使用到宏的地方,都会被替换成其指向的值
Ⅰ.格式
注意:
- 宏定义后面不加分号
;
- 宏名称用大写
#define 宏名称 宏指向的值
Ⅱ.优点
- 使程序更见见名知意
- 方便后期宏指向数据的修改
Ⅲ.例子
预编译前
使用宏定义了PI
#define PI 3.1415926
int main(){
double f;
printf("%lf\n"PI);
f = PI;
retrun 0;
}
预编译后:
在预编译后,所有使用到PI的地方,都会被替换成其指向的值
int main(){
double f;
printf("%lf\n"3.1415926);
f = 3.1415926;
retrun 0;
}
② 带参宏
在预处理的时候,参数列表内的值会替换宏体内的变量的到一个"字符串",程序中所有用到宏的地方,都会被这个字符串所替换
Ⅰ.格式
参数不用加数据类型
-
宏名称
为宏定义的名称 -
参数列表
是用逗号分隔的参数列表 -
宏体
是宏的功能代码块
#define 宏名称(参数列表) 宏体
Ⅱ.相关问题
问题:
宏,归根结底就是替换,如果给带参宏传递表达式,就很可能会造成问题
例如:带参宏是#define S(a,b) a*b
,使用时写成了S(2+4,3)
期望: 6 * 3 =18
结果: 2+4*3=2+12=14
**解决:**为宏体内每一个参数都加括号,保证运算顺序不因传入值是式子而改变
修改:#define S(a,b) (a)*(b)
Ⅲ.带参宏和带参函数的区别
带参宏:
-
节省时间,浪费空间
-
形参无数据类型
带参函数:
-
浪费时间,节约空间
-
形参有数据类型
Ⅳ.例子
编译前:
#define S(a,b) a*b
int main(){
int num;
num = S(2,4);
return 0;
}
预编译后:
预编译时代码中用到宏的地方会被宏体替换
int main(){
int num;
num = 2*4;
return 0;
}
(3)宏的终止
定义的宏不做其它操作一般是自上到下全文生效的,如果想要在代码的中间结束宏,可以使用undef
指令来终止宏的定义范围
① 格式
undef 宏名称
② 示例
#define PI 3.1415926
int main(){
double f;
printf("%lf\n"PI);//3.1415926
undef PI;
f=PI;//编译到这里会直接报错
return 0;
}
三.选择性编译
1.根据宏定义编译
ifdef
(1)已定义就编译 ① 概述
当设置的条件宏被定义后就指定代码段,反之不执行
② 格式
和 if else 语句是有区别的,if else 语句都会被编译,通过条件选择性执行代码而 选择性编译,只有一块代码被编译
它不能加花括号
{}
,切记!!!
如果条件宏是被定义的就会执行代码段一,如果是没有被定义的,就会执行代码段二
#ifdef 条件宏
代码段一
#else
代码段二
#endif
③ 示例
因为AAA宏已经被定义,所以此时只会打印helloWorld
#define AAA
int main() {
#ifdef AAA
printf("helloWorld\n");
#else
printf("helloChina\n");//因为上面的条件宏AAA已经被定义了,所以这里不会被编译
#endif
return 0;
}
ifndef
(2)未定义就编译 ① 概述
刚好和上面那个反着来
当设置的条件宏未被定义后就指定代码段,反之不执行
② 格式
如果条件宏是未被定义的就会执行代码段一,如果是已被定义的,就会执行代码段二
#ifndef 条件宏
程序段一
#else
程序段二
#endif
return 0;
}
③ 示例
因为AAA宏已经被定义,所以此时只会打印helloChina
#define AAA
int main() {
#ifndef AAA
printf("helloWorld\n");
#else
printf("helloChina\n");//因为上面的条件宏AAA已经被定义了,所以这里会被编译
#endif
return 0;
}
④ 应用
**应用:**防止头文件重复包含
**原理:**第一次引用头文件时,假设__FUN_H__
尚未被定义,那么整个#ifndef
区块下面的内容都会被编译进去。同时,#define __FUN_H__
这一行会定义__FUN_H__
,这样下次引用时就因为已定义了该宏,而不再进入#ifndef
的区块,进入达到方法头文件重复调用而导致的重复展开问题
示例头文件:
#ifndef __FUN_H__
#define __FUN_H__
extern int fun(int x,int y);
#endif
2.据真假定编译
多用于软件裁剪,根据情况决定是某编译实现某部分功能(付费功能😂)
(1)概述
顾名思义,就是根据条件的真假判断要编译那个代码段
(2)格式
如果表达式为真,编译第一段代码,否则编译第二段代码
#if 表达式
程序段一
#else
程序段二
#endif
(3)示例
当if后是1
(真)时,就执行第一个代码块,当if后是0
(假)时执行第二个代码块
int main() {
#if 1
printf("helloWorld\n");
#else
printf("helloChina\n");//因为上面的条件宏AAA已经被定义了,所以这里不会被编译
#endif
return 0;
}
四.静态库
**库的作用:**主要还是用来替换明文的库文件来完成程序的编译,多用于商业目的的库文件提供
1.静/动态编译
(1)静态编译
① 特点
**特点:**编译的时候还会把静态库文件也一并打包到可执行文件中
② 优缺点
**缺点:**可执行文件体积较大(毕竟包含了库文件)
**优点:**运行时不依赖库文件,系统环境适应性高
③ 编译指令
gcc -static hello.c -o hello
(2)动态编译
① 特点
**特点:**编译时时不会把动态库文件打包编译到可执行程序中,其与库只是编译衔接的关系
② 优缺点
**缺点:**运行时需要链接库文件,如果运行环境没有库文件,就会运行失败
**优点:**可执行文件体积小
③ 编译指令
gcc hello.c -o hello
2.创建库文件
(1)静态库
① 静态库制作
① 先直接将库文件.c
汇编成.o
文件
gcc -c mylib.c -o mylib.o
② 执行指令将汇编的文件制作成静态库
静态库起名的时候必须以 lib 开头以.a 结尾
ar rc libtestlib.a mylib.o
② 静态库的使用
使用静态库.a
替换明文代码的库文件.c
进行编译
Ⅰ.编译方式一(文件在同目录下)
将头文件和库文件放到一个目录下
gcc -static mytest.c libtestlib.a -o mytest
Ⅱ.编译方式二(文件不在目录下)
**概述:**通过指定头文件及库文件的路径完成编译
**前件:**库函数、头文件假设在/home/edu 目录
-L
:是指定库文件的路径的
-l
(小L):是指定库名的,其值只要库文件名lib后面.a前面的部分
-I
:指定头文件路径
gcc static mytest.c -o mytest -L/home/edu -ltestlib -I/home/edu
(3)动态库
① 动态链接库制作
直接使用下面的命令编译就好了
-shared
:是动态库的标记
gcc -shared mylib.c -o libtestlib.so
② 动态库的使用
动态库编译完成使用时可能会报找不到库文件,此时需要手动将库文件设置成环境变量
Ⅰ.编译一(文件在同一目录)
库文件、头文件均在当前目录下
编译命令:
gcc mytest.c libtestlib.so -o mytets
设置环境变量:(在库文件所在文件夹运行)
export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
Ⅱ.编译二(文件不在目录)
整体编译使用和静态库一样
编译命令:
库函数、头文件假设在/opt 目录
gcc mytest.c -o mytest -L/home/edu -ltestlib -I/home/edu