1.环境搭建
Linux系统、Xshell、Xftp、VSCode
用Xshell远程连接,通过命令去操作系统;用Xftp与系统进行文件传输
2.GCC
GCC(GNU Compiler Collection,GNU编译器套件)是由GNU开发的编程语言译器,包括C、C++、Objective-C、Java、Ada和Go语言前端,也包括这些语言的库。
安装命令:sudo apt install gcc g++
查看版本:gcc/g++ -v/--version
用GCC编译
查看有哪个文件ls
进入文件夹cd 文件名
创建新的文件夹mkdir 文件名
创建c代码touch 文件名.c
然后就可以去vs中编写代码
编写完成后就可以回到Xshell6进行编译,
输入gcc test.c -o app
,-o是指定生成文件的名称,app为生成文件的名称,然后输入./app
运行生成的文件
也可以输入gcc test
GCC工作流程
源代码通过预处理器进行预处理,会把源代码中导入头文件进行展开(把头文件的内容复制到源代码中),并且将源代码中的注释删除,定义的宏也会进行一些替换
预处理后源代码经过编译器编译成汇编代码,再经过汇编器转换成目标代码(计算机能够识别的1010等),再结合启动代码、库代码、其他目标代码通过链接器链接成可执行程序
在VS中编写代码:
#include <stdio.h>
#define PI 3.14
int main(){
//测试代码
int sum = PI + 10;
printf("hello world\n");
return 0;
}
在Xshell6中输入gcc test.c -E -o test.i
,-E表示对test.c继续进行预处理,生成的test.i文件部分如下,注释被删除,头文件被导入,宏被替换成3.14
在Xshell6中输入gcc test.c -S -o test.s
,-S表示对test.i进行编译
在Xshell6中输入gcc test.s -C -o test.o
,-C表示对test.i进行编译
这个代码不需要链接,输入./test.o
直接执行,输出hello world
这里其实就是解释前面用GCC编译的过程,rm 文件名
删掉刚才生成的文件,直接输入gcc test.c -S
,也会生成test.s文件
GCC常用参数选项
gcc编译选项 | 说明 |
---|---|
-E | 预处理指定的源文件,不进行编译 |
-S | 编译指定的源文件,但不进行汇编 |
-c | 编译、汇编指定的源文件,但不进行链接 |
-o [file1] [file2] / [file2] -o [file1] | 将文件file2编译成可执行文件file1 |
-I directory | 指定include包含文件的搜索目录 |
-g | 在编译的时候,生成调试信息,该程序可以被调试器调试 |
-D | 在程序编译的时候,指定一个宏 |
-w | 不生成任何警告信息 |
-Wall | 生成所有警告信息 |
-On | n的取值范围:0~3。编译器的优化选项的4个级别,-O0表示没有优化,-O1缺省值,-O3优化级别最高 |
-l | 在程序编译的时候,指定使用的库 |
-L | 指定编译的时候,搜索的库的路径 |
-fPIC/fpic | 生成与位置无关的代码 |
-shared | 生成共享目标文件,通常用在简历共享库时 |
-std | 指定C方言,如-std=c99,gcc默认的方言时GNU C |
-o的两种用法:
3.静态库
库文件时计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来使用的变量、函数或类。
库是特殊的一种程序,编写库的程序和编写一般的程序叙别不大,只是库不能单独运行。
库文件有两种,静态库和动态库(共享库),区别是:
静态库在程序的链接阶段被复制到了程序中;
动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
库的好处:1.代码保密;2.方便部署和开发。
静态库的制作
命名规则:
Linux:libxxx.a
,lib是前缀(固定)、xxx是库的名字、.a是后缀(固定)
windows:libxxx.lib
,与Linux同理
静态库的制作:
(1)gcc获取.o文件;
(2)使用ar工具(archive)将.o文件打包,ar rcs libxxx.a xxx.o xxx.o
,将xxx.o、xxx.o打包成libxxx.a
通过Xftp6将文件传入Ubuntu中
在Xshell6中将calc中的add.c、div.c、mult.c、sub.c打包成静态库
head.h头文件不需要转成.o形式,在预编译的时候会被复制到.c文件中
main.c是测试文件也不需要要转换
tree
的作用是查看当前文件夹的目录
首先通过gcc -c add.c div.c mult.c sub.c
将add.c、div.c、mult.c、sub.c转换成.o形式,
然后通过ar rcs libcalc.a add.o div.o mult.o sub.o
将add.o div.o mult.o sub.o打包成静态库。
静态库的使用
在Linux文件夹下创建lesson05文件夹,
输入cp -r calc/ library/ ../lesson05
,将lesson04文件夹下的calc、library复制到上级目录下的lesson05文件夹
进入到lesson05的library文件夹中可以看到其分包,其中include中包含的是需要用到的头文件、main.c是测试文件、src包含的是源代码文件
将leeson04中的library.a拷贝到lesson05,输入cp ../calc/library.a ./lib/
,即将上一级目录(lesson05)下的calc文件夹中的library.a拷贝到当前目录下的lib文件夹下
使用库文件时也需要库文件依赖的头文件
然后对main.c进行编译,
如果输入gcc -o main.c app
就会报错,因为在预处理时需要将head.h中的内容拷贝到main.c中,但现在使用的是相对路径(main.c的目录),找不到head.h;
如果输入gcc -o main.c app -I ./include/
,通过-I diretory
指定include包含文件的搜索目录。此时提供了头文件,但是函数的定义在library.a中。
因此要输入gcc mai.c -o app -I ./include/ -l calc -L ./lib
,
通过- l
在程序编译的时候,指定使用的库,注意后面跟的是库的名称,而不是库文件的名字
通过- L
指定编译的时候,搜索的库的路径
然后就可以执行app文件
4.动态库
动态库的制作及使用
命名规则:
Linux:libxxx.so
,lib是前缀(固定)、xxx是库的名字、.so是后缀(固定)
windows:libxxx.dll
,与Linux同理
静态库的制作:
(1)gcc获取.o文件;得到和位置无关的代码,即gcc -c -fpic/-fPIC a.c b.c
(2)gcc得到动态库,即gcc -shared a.o bo. -o libcalc.so
将lesson05文件夹中的calc、library拷贝到lesson06,cp calc library ../lesson06 -r
,因为是目录,因此要加上-r
使其递归的拷贝
删除calc中的.o、.a文件,lib中的libcara.a文件
*.o
中的*
表示通配符
进入calc制作动态库
在linux中绿色显示的文件一般都是可执行文件
将制作好的动态库文件放入library/lib中
然后对main.c进行编译
动态库加载失败的原因及解决方法
在运行编译好的main文件时会报错,提示加载动态库时找不到动态库文件
静态库:GCC 进行链接时,会把静态库中代码打包到可执行程序中
动态库:GCC 进行链接时,动态库的代码不会被打包到可执行程序中
程序启动之后,动态库会被动态加载到内存中,通过 ldd (list dynamic
dependencies)命令检查动态库依赖关系
当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统的动态载入器来获取该绝对路径。
对于elf格式的可执行程序,是由ld-linux.so来完成的,它先后搜索elf文件的 DT_RPATH段 ——> 环境变量D_LIBRARY_PATH ——> /etc/ld.so.cache文件列表 ——> /lib/,/usr/lib目录找到库文件后将其载入内存。
动态库加载失败就是因为没有指定动态库的绝对路径,
DT_RPATH段修改不了,
因此先配置环境变量D_LIBRARY_PATH:
在终端输入export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库的绝对路径
动态库的绝对路径通过pwd
获取
配置完成后可以通过echo 环境变量的名称
来查看绝对路径
配置完环境变量后再通过ldd main
检查动态库依赖关系
main程序也就可以运行了
但是在终端中配置环境变量,在关闭终端后环境变量就失效了。
为了避免这种情况,有两种方法永久配置环境变量:用户级别配置和系统级别配置
用户级别配置:
可以通过home目录下的隐藏文件.bachrc配置环境变量,vim .bashrc
编辑
在.bashrc中已经存在一些环境变量,按shift+g
跳转到最后一行,按o
插入新的一行,输入export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库的绝对路径
,然后输入:wq
保存并退出,最后输入source/. .bashrc
使其生效
系统级别配置:
系统级别配置和用户级别差不多,只不过换了个文件,source vim /etc/profile
,然后输入export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库的绝对路径
,最后输入source/. /etc/profile
使其生效
还可以修改 /etc/ld.so.cache文件
如果直接输入vim /etc/ld.so.cache
会显示二进制文件
所以通过sudo vim /etc/ld.so.conf
来进行修改,修改方式同上
不推荐修改 /lib/,/usr/lib,因为里面存在很多系统库文件,防止误操作
静态库和动态库的对比
5.Makefile
Makefile 文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就像一个 Shell 脚本一样,也可以执行操作系统的命令。
Makefile 带来的好处就是“自动化编译” ,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个解释 Makefile 文件中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如 Delphi 的 make,Visual C++ 的 nmake,Linux 下 GNU 的 make。
Makefile文件制作
文件命名:makefile 或者 Makefile
Makefile规则:
在lesson07下中如果要手动编译,就要输入gcc add.c div.c main.c mult.c sub.c -o app
也可以写一个Makefile进行自动编译:
首先创建一个Makefile文件vim Makefile
,
然后输入
app:add.c div.c main.c mult.c sub.c //目标:依赖
gcc add.c div.c main.c mult.c sub.c -o app //命令
然后输入make
执行make指令,需要sudo apt install make
安装
make
执行后就会编译成一个可执行程序
Makefile工作原理
1.命令在执行之前,需要先检查规则中的依赖是否存在:
- 如果存在,执行命令
- 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令
例如在lesson07中,修改Makefile文件:
add.o等在文件中并不存在,那么就会向下检查其他命令,如
add.o:add.c gcc -c add.c -o add.o
是生成add.o的规则,那么就会执行该规则命令
通过执行make命令可以看出执行顺序
2.检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间:
- 如果依赖的时间比目标的时间晚,需要重新生成目标
- 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行
如果再次执行make命令,会提示app已是最新
如果修改任意.c文件,如add.c,
再次执行make命令,会进行检查更新,即时间的对比
Makefile写法的优化
变量
自定义变量:变量名=变量值 var=hello
预定义变量:
AR : 归档维护程序的名称,默认值为 ar
CC : C 编译器的名称,默认值为 cc
CXX : C++ 编译器的名称,默认值为 g++
$@ : 获取目标的完整名称,$@
$< : 获取第一个依赖文件的名称,$<
$^ : 获取所有的依赖文件,$^
注: @ 、 @、 @、< 、$^为自动变量,只能在规则的命令中使用
如:
app:main.c a.c b.c
gcc -c main.c a.c b.c -o app
//可以替换成
app:main.c a.c b.c
$(CC) -c $^ -o $@
获取变量的值:$(变量名)
,如$(var)
因此可以用变量对前面的Makefile文件进行简化
模式匹配
%.o:%.c
:
%: 通配符,匹配一个字符串;两个%匹配的是同一个字符串
如:
add.o:add.c
gcc -c add.c -o add.o
//通过模式匹配
%.o:%.c
$(CC) -c $< -o $@
函数
$(wildcard PATTERN...)
功能:获取指定目录下指定类型的文件列表
参数:PATTERN 指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔
返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
示例:$(wildcard *.c ./sub/*.c)
,返回值格式: a.c b.c c.c d.c e.c f.c
$(patsubst <pattern>,<replacement>,<text>)
功能:查找
模式
<pattern>
,如果匹配的话,则以
<replacement>
替换
<pattern>
可以包括通配符
%
,表示任意长度的字串。如果
<replacement>
中也包含
%
,那么,
<replacement>
中的这个
%
将是
<pattern>
中的那个
%
所代表的字串。(可以用
\
来转义,以
\%
来表示真实含义的
%
字符)
返回:函数返回被替换过后的字符串
示例:
$(patsubst %.c, %.o, x.c bar.c)
,返回值格式: x.o bar.o
执行完make命令后会生成非常多的.o文件,但是并不需要这些文件,因此可以继续修改makefile文件,保存后通过
make clean
去运行clean规则删除.o文件