目录
Linux上常用的编译器gcc\g++
如何使用gcc/g++
编译过程:
如何使用gcc编译?
进行预处理
进行编译
进行汇编
进行链接
函数库
函数库的分类
gcc选项
Linux调试器-gdb的使用
gdb的常用参数
Linux项目自动化构建工具make/Makefile
原理
利用上述知识做一个简单的进度条程序
Makefile
进度条函数的实现(.c文件)
进度条的头文件
进度条的主函数
Linux上常用的编译器gcc\g++
-
GCC(GNU Compiler Collection):GCC 是一个功能强大且广泛使用的编译器套件,在 Linux 系统中被广泛采用。它支持多种编程语言,如 C、C++、Objective-C、Fortran 等,是许多开源项目和软件的首选编译工具。
-
G++:G++ 实际上是 GCC 的 C++ 编译器版本,专门用于编译 C++ 代码。它提供了对 C++ 标准的良好支持,并且与 GCC 一起安装在许多 Linux 发行版中。
如何使用gcc/g++
-
在了解如何使用gcc和g++编译代码之前,我们先了解一下编译的过程
编译过程:
-
预处理阶段:预处理阶段的主要任务是宏定义、文件包含、条件编译、去除注释(展开头文件、去注释、条件编译、宏替换)等。这时候会生成以.i为后缀的文件
-
编译阶段(生成汇编代码):这个阶段首先检查代码的规范性、是否有语法错误等,确定无误后将代码翻译为汇编语言。这时会将.i文件转换成.s文件
-
汇编(将汇编代码转换成机器码):将编译生成的.s文件转成目标文件。
-
链接:编译成功之后就进入链接阶段,这时会生成可执行文件或者库文件。
如何使用gcc编译?
-
通用格式
gcc 【选项】 要编译的文件 【选项】 目标文件
-
未编译前的状态,此时只有一个我们写好的.c文件
进行预处理
-
我们使用下面的命令对文件进行预处理,其中选项【-E】的作用是让gcc在预处理结束后停止编译过程,【-o】是指目标文件,【.i】文件为已经预处理过的文件
gcc -E test.c -o test.i
进行编译
-
我们使用下面的命令对文件进行编译,其中选项【-S】的作用是让gcc在编译结束后停止汇编过程,【-o】是指目标文件,【.s】文件为已经预处理过的文件
gcc -S test.i -o test.s
进行汇编
-
我们使用下面的命令对文件进行汇编
gcc -c test.s -o test.o
进行链接
-
我们使用下面的命令对文件进行链接
gcc test.o -o test
-
如此我们便完成了程序的编译,文件后缀的变更顺序为iso(和摄影中的感光的一样)
函数库
-
不知道大家有没有发现一个问题,我们的c程序中可并没有printf的函数实现,头文件stdio中也只有它的声明,并没有它的实现,它是在哪里实现的呢?
-
系统把这些函数实现都放在libc.so.6的库文件中去了,在没有特别指定的情况下gcc回去系统默认的搜索路径/usr/lib下进行查找,这便是链接。
-
通过ldd命令查看可执行程序所以来的库
函数库的分类
-
函数库分为两类:静态库和动态库。
-
静态库是指在编译链接时,把库文件的代码全部加入到可执行文件当中去,这样做的结果就是生成的文件会比较大,但运行时也不再需要库文件了,静态库一般后缀为.a
-
动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序资兴市有运行时链接文件加载库,这样可以节省系统开销。动态库一般后缀为.so。gcc在编译时默认使用动态库。
gcc选项
-
【-E】:只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面
-
【-S】:编译到汇编语言而不进行汇编和连接操作
-
【-c】:编译到目标代码
-
【-o】:文件输出到文件
-
【-static】:此选项对生成的文件采用静态链接
-
【-g】:生成调试信息。GNU调试器可利用该信息
-
【-shared】:此选项将尽量使用动态库,生成文件较小,但要保证有动态库
-
【-O0/-O1/-O2/-O3】:编译器的四个优化选项,-O0表示没有优化,默认优化级别是O1,O3是级别最高的优化
-
【-w】:不生成任何警告信息
-
【Wall】:生成所有警告信息
Linux调试器-gdb的使用
-
使用gcc/g++编译出来的程序默认是release版本
-
想对一个文件使用gdb进行调试的话,首先要保证该文件是可以被调试的(编译的时候要带上【-g】)
gdb的常用参数
Linux项目自动化构建工具make/Makefile
-
Makefile的书写格式
-
target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。prerequisites就是,要生成那个target所需要的文件或是目标。command也就是make需要执行的命令。(任意的Shell命令)这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。
-
一个工程中的源文件不计其数,按照类型、功能、模块分别放在若干个目录中,按照上文所讲的方法,无疑是对程序员的酷刑。
-
makefile定义了一系列规则来指定,那些文件需要被先编译,那些文件现需要后编译,甚至与进行更复杂的功能操作
-
makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大地提高了软件开发效率
-
make是一个命令工,makefile是一个文件,两者搭配使用,完成项目的自动化构建
原理
-
make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
-
如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“hello”这个文件,并把这个文件作为最终的目标文件。
-
如果hello文件不存在,或是hello所依赖的后面的hello.o文件的文件修改时间要比hello这个文件新(可以用 touch 测试),那么,他就会执行后面所定义的命令来生成hello这个文件。
-
如果hello所依赖的hello.o文件不存在,那么make会在当前文件中找目标为hello.o文件的依赖性,如果找到则再根据那一个规则生成hello.o文件。(这有点像一个堆栈的过程)
-
当然,你的C文件和H文件是存在的啦,于是make会生成 hello.o 文件,然后再用 hello.o 文件声明make的终极任务,也就是执行文件hello了。
-
这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
-
在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。
-
make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
利用上述知识做一个简单的进度条程序
Makefile
progress_v2: progress_v2.c test_v2.cgcc $^ -o $@
.PHONY:clean
clean:rm -f progress_v2
进度条函数的实现(.c文件)
#include"progress_v2.h"void download()
{int haveDownload = 0;while(haveDownload < ALLBYTE){progress_v2((haveDownload*100.0)/ALLBYTE);usleep(95000);haveDownload += PERDOWNLOAD;}progress_v2(100);printf("\n");
}void progress_v2 (double rate)
{static char arr1[4]="/-\\";//模拟进度条运行static char arr[101] = {0};//作为进度条显示数组arr[(int)rate] = '#';fflush(stdout);printf("[%-101s][%.1lf%%][%c]\r",arr,rate,arr1[(int)rate%4]);
}
进度条的头文件
#include<stdio.h>
#include<unistd.h>#define ALLBYTE 1000
#define PERDOWNLOAD 7
void download();void progress_v2(double rate);
进度条的主函数
#include"progress_v2.h"int main()
{download();return 0;
}