一、gcc编译器
1.1基本使用规范
gcc+[文件名]
回车
会自动生成文件名为a.out的可执行程序
1.2编译链接过程用到的选项
1.2.1指定生成名字的选项-o
gcc -o +[指定名字] +[文件名]
可以生产指定名字的可执行程序
1.2.2预处理选项-E
如gcc -o test.i -E test.c
可进行预处理,完成宏替换等操作,并存到test.i中
1.2.3编译选项-S
如gcc -o test.s -S test.i
可进行编译操作,将代码转换为汇编语言
1.2.4汇编选项-c
如gcc -o test.o -c test.s
可进行汇编操作,将汇编语言转化为机器可识别代码
1.2.5链接
完成了预处理,编译,汇编后的.o文件,可以直接进行链接形成可执行程序
只需gcc -o test.exe test.o
1.3标准库查看指令ldd
ldd指令可以进行库文件的查看
使用规范:
ldd+[文件名]
如:
1.3补:常用的标准库libc.so.6
此时的test可执行程序依托于libc.so.6标准库,同时Linux中的许多指令也同样依托于它
如:
二、动态/静态库与动态/静态链接
2.1动态/静态库是什么
动态库:以.so为后缀的库,称为动态库,在Windows中为.dll
静态库:以.a为后缀的库,称为静态库,在Windows中为.lib
2.1补:①先有语言还是先有编译器
先有编译器,最开始是用二进制代码写出来第一个编译器(汇编语言编译器),此后再通过编译器自举(即使用汇编语言在当前编译器下写其他语言编译器)来完成其他语言的编译器
②为什么编译过程要经历汇编这一阶段?而不是直接转化为机器可识别代码
将语言转化为机器可识别代码是一个巨大的工程,其中有着无数的问题,而转为汇编语言就要简单得多
并且汇编语言转化机器可识别代码的工作完成进度很高,为了方便所以选择经历转化汇编这一阶段
2.2动态/静态链接是什么
2.2.1动态链接
编译器提前加载动态库中所需代码的地址,程序运行过程中走到对应的一步会跳转到动态库中
2.2.2静态链接
把我们要访问的方法从静态库中拷贝到自己的可执行程序当中
2.2.3各自的优缺点
①动态库
优点:节省资源,可执行程序大小较小
缺点:一旦动态库缺失,程序会无法运行;要进行函数跳转,可执行程序耗时比较长
②静态库
优点:不依赖其他库,即使库丢失依旧可以运行
缺点:比较浪费资源,可执行程序大小较大
2.2.3补:库和链接有什么关系
要想进行对应的链接,必须要有对应的库
2.3连接方式的查看与选择
2.3.1查看连接方式的指令file
使用规范为
file+[可执行程序名]
如:
从中可以看出连接方式:
动态链接为dynamically linked
静态链接为statically linked
2.3.2链接方式的选择
默认生成采用动态链接
若要更改为静态链接,需要使用-static选项
如:
gcc -o sta_test test.c -static
2.3.2补:云服务器默认没有安装C/C++的静态标准库
c:yum install -y glibc-static
c++:yum install -y libstdc++-static
三、自动化构建工具make/makefile
3.1自动化构建工具的意义
我们在Linux中进行编译链接形成可执行程序可每次都需要写一行gcc指令,有没有更简单的方法呢?
有的,利用make和makefile进行配合即可简化这一步
3.2make/makefile是什么?应该怎么使用?
make是一个指令
makefile是一个文件
使用样例:
假如在当前目录中有一个文件test.c
我们只需要创建一个文件makefile
在其中写入
test:test.c
gcc -o test test.c
然后执行指令
make
即可自动形成test可执行程序
3.3makefile的本质与组成
⭐3.3.1通过例子拆分组成结构
结合例子
makefile的本质就是依赖关系和依赖方法的集合
二者需要紧跟着写,且依赖方法要以tab开头
3.3.2自动清理的配置
在使用如VS2022等编译器的时候,我们编译运行时自动生成可执行文件后会自动清理,那么在Linux中可不可以实现呢?
可以的,makefile可以帮助我们完成
方法是:
在makefile文件后添加
.PHONY:clean
clean:
rm -f test
在外面运行指令
make clean
即可实现自动删除可执行文件
其构成解析为:
①
②
make clean的原理是:
形成clean目标文件的时候会自动执行其依赖方法
3.3.3依赖方法可以是任意指令,且默认回显
执行依赖方法时,任意指令都可以支持,且默认将代码回显
可以选择跳过回显:每行前面加一个@
例如:
test:test.c
@echo "开始编译"
@gcc -o test test.c
@echo "编译完成"
可以实现make的时候显示“开始编译”
若代码编译一切顺利会形成test可执行程序并显示“编译完成”
3.3.4.PHONY的功能是什么
可以让目标文件对应的方法总是被执行
例如gcc执行前的比较过程就会被忽略(参考3.4.2)
例1:在makefile中
.PHONY:test
test:test.c
gcc -o test test.c
这样以后在形成test可执行程序的时候不会再关注时间的比较,而绝对执行gcc更新可执行程序
例2:在3.3.2的例子中
我们删除.PHONY:clean并不影响功能
但这仅仅是因为rm本就不关心时间,不会被限制执行,可是往往clean中并不只是rm一行,在那个时候就需要声明伪目标了
3.4make的使用过程
3.4.1stat指令查看文件属性来读取时间的原理
stat+[文件名]
可以查看当前文件的详细属性
其中有三个时间
Access:最近访问时间(参考价值较低)
Change:最近文件属性被改变时间
Modify:最近文件内容被改变时间
3.4.2在使用make时,gcc编译过程连续运行的话有时候会提示不让编译,为什么?
在运行gcc编译指令之前,会对比源文件和可执行程序的Modify时间,若可执行程序的Modify时间更晚,则不会进行编译
⭐3.5makefile/make的基本原理
①makefile文件会被make指令从上到下扫描,如果单独执行make指令,会执行第一对依赖关系和依赖方法;若要执行其他组,需要make+[目标名]
②makefile执行gcc的过程中,如果发生了语法错误,会终止推导过程
③make解释makefile时时自动推导的,会一直根据目标文件推到到需要目标文件的一步,再逆向执行依赖方法
④make默认只会执行一个可执行程序
3.6make和makefile的其他自动功能
3.6.1自动由目标文件向依赖文件推导
其实我们要由test.c生成test可执行程序是需要多步的,完整起来写应为
test:test.o
gcc -o test test.o
test.o:test.s
gcc -c test.s -o test.o
test.s:test.i
gcc -S test.i -o test.s
test.i:test.c
gcc -E test.c -o test.i
.PHONY:clean
clean:
rm -f test test.o test.s test.i
根据3.5中基本原理③,之所以可以直接写test:test.c是因为make的自动推导
3.6.2makefile中也可以直接定义变量,其中变量名有独特的使用方法
其方法是:
[变量名]=[文件名]
例如对于3.6.1中代码(我们仅体现两步test->test.o test.o->test.c)
原代码:
test:test.o
gcc -o test test.o
test.o:test.c
gcc -c test.c -o test.o
.PHONY:clean
clean:
rm -f test test.o
使用类型定义后:
bin=test
src=test.o
$(bin):$(src)
gcc $^ -o $@
%.o:%.c
gcc -c %<
.PHONY:clean
clean:
rm -f $(bin) test.o
我们对其中细节进行一下解释:
3.6.3一次性形成两个可执行程序的方法
原理是通过声明一个伪目标,使其依赖文件同时包含两个可执行程序
但不包含任何依赖方法
如:
bin1=test1
src1=test1.c
bin2=test2
src2=test2.c
.PHONY:all
all:$(bin1) $(bin2)
$(bin1):$(src1)
gcc $^ -o $@
$(bin2):$(src2)
gcc $^ -o $@
.PHONY:clean
clean:
rm -f $(bin1) $(bin2)
四、综合样例
4.1知识补充:回车和换行是一个概念吗?
不是一个概念,回车指光标回到一行起始,而换行指光标直接移到下一行
(键盘中的回车其实本质上是:先回车再换行,C语言中的\n也是做了特殊处理,完成回车加换行)
4.2知识补充:缓冲区的问题
假如有这样一组程序
①
printf("hello world");
sleep(2);
②
printf("hello world\n");
sleep(2);
运行起来的结果是
①两秒后出现hello world同时程序结束
②直接出现hello world,两秒后程序结束
我们可以确定printf一定会早于sleep执行,可为什么会出现这一差异呢?
实际上,\n进行了一次强制缓冲区刷新
缓冲区图示:
4.2补:
①sleep函数:程序暂停
在Linux中,头文件#include<unistd.h>下有
1>sleep函数
2>usleep函数
他们的传入值是int,作用是让程序暂停一段时间,其单位对应是:
sleep:秒
usleep:微秒(1000000微秒=1秒)
②指定刷新缓冲区
可以利用#include<stdio.h>下的
int fflush(FILE* stream)
函数,来制定刷新缓冲区
需要传入一个stdout流
4.3简单的计时器程序
根据上述知识储备,可以完成一个简单的计时器程序
需要注意几个细节:
①打印时用\r来覆盖,用fflush来刷新缓冲区
②用sleep做出间隔
③printf(“-2d\r”,...)来保证打印的数字始终靠左,且二位数也不受影响
④循环完成利用\n完成一次换行
源码:
makefile:
bin = test
src = test.ctest:test.cgcc $^ -o $@
.PHONY:clean
clean:rm -f test
test.c
#include <stdio.h>
#include <unistd.h>int main()
{int num=10;while(num >= 0){printf("%-2d\r",num);num--;fflush(stdout);sleep(1);}printf("\n");return 0;
}