C语言编译步骤
预处理
编译
汇编
链接
预处理
概念:
预处理就是在源文件(如.c文件)编译之前,所进行的一部分预备操作,这部分操作是由预处理程序自动来完成;当源文件在编译时,编译器会自动调用预处理程序来完成对预处理指令的解析,预处理指令解析完成才能进入下一步的编译过程。
我们为了能够方便的看到这个编译细节,我们可以使用下面命令:
gcc 源文件 -E -o 程序名[.后缀]
预处理的功能
宏定义
不带参数的宏定义
语法:
#define 宏名 常量数据
预处理:
此时的预处理只做数据替换,不做类型检查
注意:
我们定义的宏是不会占用内存空间,还没有到编译环节,就已经被替换成了我们宏中的常量数据
带参数的宏定义
语法:
#define 宏名(参数列表) 参数表达式
面试题:
#define multi(a,b) a * b
/**
* 宏定义-带参数
*/
#include <stdio.h>
#define MULTI(a,b) a * b
int main()
{
int result = MULTI(7+2,3);
printf("%d\n",result);// 13
return 0;
}
宏定义的作用域
#define 命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束。通常,#define命令写在文件开头,函数之前,作为文件一部分,在此文件范围内有效。
可以用 #undef 命令终止宏定义的作用域
文件包含
概念:
所谓“文件包含”处理是指一个源文件可以将另外一个源文件的全部内容包含进来。这个适用于多文件开发。
预处理
此时的预处理,是将文件中的内容替换,文件包含指令。
包含方式:
第1种:
#include<xxxx.h>
系统会到标准库头文件目录(Linux下/usr/include )查找包含的文件
第2种:
#include"xxxx.h"
在当前工程路径下查找,如果未找到,系统会到标准库头文件目录查找
案例:
algorithm.h
/**
* 自定义头文件,专门用于存放被外部访问的函数的声明
*/
// 数组的累加和计算
extern int sum(const int *p,int len);
algorithm.c
/**
* 实现数组元素的累加计算
*/
int sum(const int *p,int len)
{
int sum = 0;
register int i = 0;
for(;i len; i++)
{
sum += *(p+i);
}
return sum;
}
app.c
// #include
// 引入自定义的头文件
#include "algorithm.h"
// 如果有n多个外部函数,难道都要一个个的使用extern进行声明?
// 引入外部函数声明
// extern int sum(const int*,int);
int main()
{
int arr[5] = {12,33,14,55,34};
int res = sum(arr,5);
printf("数组累和结果是:%d\n",res);
return 0;
}
编译命令:
gcc algorithm.c app.c -o app // 有包含关系的c文件要一起编译
库文件
库文件概述
什么是库文件:
库文件本质上是经过编译后生成的二进制代码文件,库文件不能独立运行,需要被依赖(被调用)
当库中的函数被调用时,该库需要加载进内存,库中的函数体会执行,将这一类代码打包成一个文件,这个文件也称为库文件
库文件大量存在与操作系统(window,linux, MacOS)和应用中(QQ, 英雄联盟等)
库文件的分类
1.静态库
windows : xxxx.lib library(库)
Linux :libxxx.a /lib或者/usr/lib
2.动态库(共享库)
windows : xxxx.dll (Dynamic Link Library动态链接库)
Linux : libxxx.so
库文件的使用
Linux系统库文件的命名规范:
静态库:libxxxx.a
动态库:libxxxx.so
1.静态库文件
1.将*.c通过编译,但不链接生成.o文件
格式:gcc -c *.c -o *.o
2.用ar命令将生成的*.o文件打包成库文件libXxxx.a
格式:ar -csr libXxxx.a *.o, *.o
3.使用库文件
格式:gcc 主程序(main.c) -o 可执行文件名 -lXxxx -L./
4.产生的可执行文件,在不依赖静态库的情况下就可以直接运行
格式:./可执行文件名
2.动态库文件
1.将*.c通过编译,但不链接生成.o文件
格式:gcc -fPIC -c *.c -o *.o
2.通过gcc指定参数 -shared 将生成的.o文件打包成.so文件
格式:gcc -shared *.o -o libXxxx.so
3.使用动态库
方式1:
在可执行文件运行时,默认会去/usr/lib,或者/lib中找动态库文件,所以要将动态库文件复制到这两个文件中的任意一个下
格式:sudo cp libXxxx.so /lib
方式2:
临时修改环境变量,将动态库存放的路径添加到环境变量中,系统在运行时会根据环境变量的配置查找库文件
在终端命令行执行以下代码:
格式:
export LD_LIBRARY_PATH=$LD_LIBRARY:$(pwd)
或者
export LD_LIBRARY_PATH=$LD_LIBRARY:库文件所在目录的绝对路径
可以通过env命令查看环境变量中的配置项---确认配置无误
临时修改环境变量,在终端关闭后配置项不起作用
方式3:永久配置:修改环境变量的文件~/.bashrc
sudo vim ~/.bashrc
给这个文件的末尾加一下代码行:
export LD_LIBRARY_PATH=:库文件所在目录的绝对路径
然后在终端执行,用于刷新文件让配置生效:
source ~/.bashrc
以上三种方式任选其一执行后,再编译主程序:
格式:gcc 主程序(main.c) -o 可执行文件名称 -lXxxx -L./
4.产生的可执行文件
格式:./可执行文件名
静态库和动态库的特点
1.静态库和动态库在产生可执行文件的原理不同(静态库将函数代码加入到主程序,动态库将函数标记加入到主程序)
2.静态库在使用时产生的可执行文件比动态库使用产生的可执行文件容量大
3.静态库叉树的可执行文件在运行时不再依赖库文件,而动态库产生的执行文件在运行时还需要使用库文件
4.扩展性和兼容性不一样: 如果程序中经常变化的功能建议用动态库,不经常变的功能建议用静态库
5.加载速度不一样:动态库在使用时加载进内存(懒加载思想),静态库在程序加载时就进内存了
6.动态库可以被多个程序共享使用
综上,动态库相比静态库使用的比较多
条件编译
概念
根据设定的条件选择待编译的语句代码。
预处理
将满足条件的语句进行保留,不满足条件的语句进行删除,交给下一步编译
语法:
语法一:
#ifdef 标识——判断标识没有定义执行ifdef语句,定义执行else
...
#else
...
#endif
语法二:
#ifndef 标识——判断标识没有定义执行ifdef语句,定义执行else
...
else
...
#endif
语法三:
#if 表达式——根据表达式返回的结果:0不成立(执行else语句),1成立(执行if语句)
...
#else
...
#endif
避免头文件重复包含的方法
语法:
#ifndef __XXXX_H
#define __XXXX_H
...
#endif
案例:
algorithm.h
/**
* 自定义头文件,专门用于存放被外部访问的函数的声明
*/
#ifndef __ALGORITHM_H
#define __ALGORITHM_H
// 数组的累加和计算
extern int sum(const int *p,int len);
#endif