Linux编译器--gcc和g++使用

gcc和g++使用

  • 一、gcc/g++的作用
    • 1.1 预处理
    • 1.2 编译
    • 1.3 汇编
    • 1.4 链接
  • 二、静态库和动态库
  • 三、make/Makefile
    • 3.1 make/Makefile
    • 3.2 依赖关系和依赖方法
    • 3.3 多文件编译
    • 3.4 make原理
    • 3.5 项目清理
  • 四、linux下的第一个小程序-进度条
    • 4.1 行缓冲区的概念
    • 4.2 \r和\n
    • 4.3 进度条代码

一、gcc/g++的作用

gcc和g++分别是GUN的C和C++的编译器,gcc和g++在执行编译的时候一般有以下四个步骤:

1.预处理(a.去注释 b.宏替换 c.头文件展开 d.条件编译)–(还是C语言)
2.编译(C代码翻译成->汇编语言)
3.汇编(汇编代码转换成->可重定向二进制目标文件)
4.链接(将汇编过程产生的二进制代码进行链接,将多个.o,.obj 合并形成一个可执行)

gcc只能用来编译C语言,g++可以编译C语言也可以编译C++
没有安装则可使用下面命令进行安装:

sudo yum install -y gcc-c++

1.1 预处理

gcc -E mytest.c -o mytest.i

Linux编译器--gcc和g++使用_第1张图片

  • 预处理功能主要包括头文件展开、去注释、宏替换、条件编译等。
  • 预处理指令是以#开头的代码行。
  • -E选项的作用是让gcc/g++在预处理结束后停止编译过程。
  • -o选项是指目标文件,“xxx.i”文件为已经过预处理的原始程序。

Linux编译器--gcc和g++使用_第2张图片

1.2 编译

gcc -S mytest.i -o mytest.s 

Linux编译器--gcc和g++使用_第3张图片

  • 在这个阶段中,gcc/g++首先检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,将代码翻译成汇编语言。
  • 用户可以使用-S选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码
  • -o选项是指目标文件,“xxx.s”文件为已经过翻译的原始程序。
    Linux编译器--gcc和g++使用_第4张图片

1.3 汇编

gcc -c mytest.s -o mytest.o

Linux编译器--gcc和g++使用_第5张图片

  • 汇编阶段是把编译阶段生成的“xxx.s”文件转成目标文件
  • 使用-c选项就可以得到汇编代码转化为“xxx.o”的二进制目标代码了

1.4 链接

gcc mytest.c -o mytest

在这里插入图片描述

  • 在成功完成以上步骤之后,就进入了链接阶段。
  • 链接的主要任务就是将生成的各个“xxx.o”文件进行链接,生成可执行文件。
  • gcc/g++不带-E、-S、-c选项时,就默认生成预处理、编译、汇编、链接全过程后的文件。
  • 若不用-o选项指定生成文件的文件名,则默认生成的可执行文件名为a.out

注意:链接后生成的也是二进制文件

二、静态库和动态库

函数库一般分为静态库和动态库两种

  • 静态库是指编译链接时,把库文件的代码全部加入到可执行文件当中,因此生成的文件比较大,但在运行时就不再需要库文件了,静态库一般以.a为后缀。
  • 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件当中,而是在程序运行时由链接文件加载库,这样可以节省系统的开销,动态库一般以.so为后缀。

动态链接
优点:省空间(磁盘的空间,内存的空间),bin体积小,加载速度快。
缺点:依赖动态库,程序可移植性较差。
静态链接
优点:不依赖第三方库,程序的可移植性较高
缺点:浪费空间

gcc和g++默认生成的二进制程序是动态链接的,我们可以使用file指令进行查看。其次,我们还可以使用ldd指令查看动态链接的可执行文件所依赖的库。
在这里插入图片描述
虽然gcc和g++默认采用的是动态链接,但如果我们需要使用静态链接,带上-static选项即可。

gcc test.c -o mytest-s -static

Linux编译器--gcc和g++使用_第6张图片
在这里插入图片描述
此时生成的可执行文件就是静态链接的了。我们可以查看源代码相同,但链接方式不同而生成的两个可执行程序mytest和mytest-s的大小。这也证明了动态链接比较节省空间,而静态链接比较浪费空间。

你的机器可能因为没有静态库,而导致链接失败!
动态链接必须使用.so动态库文件

sudo yum install -y glibc-static  #C静态库

静态链接必须使用.a静态库文件

sudo yum install -y libstdc++-static  #C++静态库

三、make/Makefile

3.1 make/Makefile

  • 会不会写Makefile,从侧面说明了一个人是否具备完成大型工程的能力。
  • 一个工程的源文件不计其数,按照其类型、功能、模块分别放在若干目录当中,Makefile定义了一系列的规则来指定:哪些文件需要先编译,哪些文件需要后编译,甚至于进行更复杂的功能操作。
  • Makefile带来的好处就是“自动化编译”,一旦写好,只需一个make命令,整个工程完全自动编译,极大地提高了软件开发的效率。
  • make是一个命令工具,是一个解释Makefile当中指令的命令工具,一般来说,大多数的IDE都有这个命令,例如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,Makefile都成为了一种在工程方面的编译方法。
  • make是一条命令,Makefile是一个文件,两个搭配使用,完成项目自动化构建。

3.2 依赖关系和依赖方法

在使用make/Makefile前我们首先应该理解各个文件之间的依赖关系以及它们之间的依赖方法。
依赖关系:文件A的变更会影响文件B,那么就称文件B依赖于文件A。

  • 例如,test.o文件是由test.c文件通过预处理、编译以及汇编之后生成的文件,所以test.c文件的改变会影响test.o,所以说test.o文件依赖于test.c文件

依赖方法:如果文件B依赖于文件A,那么通过文件A得到文件B的方法,就是文件B依赖于文件A的依赖方法。

  • 例如,test.o依赖于test.c,而test.c通过gcc -c test.c -o
    test.o 指令就可以得到test.o,那么test.o依赖于test.c的依赖方法就是gcc -c test.c -o test.o

3.3 多文件编译

当你的工程当中有多个源文件的时候,应该如何进行编译生成可执行程序呢?
首先,我们可以直接使用gcc指令对多个源文件进行编译,进而生成可执行程序。
但进行多文件编译的时候一般不使用源文件直接生成可执行程序,而是先用每个源文件各自生成自己的二进制文件,然后再将这些二进制文件通过链接生成可执行程序。
原因

  • 若是直接使用源文件生成可执行程序,那么其中一个源文件进行了修改,再生成可执行程序的时候就需要将所有的源文件重新进行编译链接。
  • 而若是先用每个源文件各自生成自己的二进制文件,那么其中一个源文件进行了修改,就只需重新编译生成该源文件的二进制文件,然后再将这些二进制文件通过链接生成可执行程序即可。

注意:编译链接的时候不需要加上头文件,因为编译器通过源文件的内容可以知道所需的头文件名字,而通过头文件的包含方式(“尖括号”包含和“双引号”包含),编译器可以知道应该从何处去寻找所需头文件。

但是随着源文件个数的增加,我们每次重新生成可执行程序时,所需输入的gcc指令的长度与个数也会随之增加。这时我们就需要使用make和Makefile了,这将大大减少我们的工作量。

步骤一:在源文件所在目录下创建一个名为Makefile的文件。
Linux编译器--gcc和g++使用_第7张图片
步骤二:编写Makefile文件
Makefile文件最简单的编写格式是,先写出文件的依赖关系,然后写出这些文件之间的依赖方法,一次写下去。
Linux编译器--gcc和g++使用_第8张图片
编写完毕Makefile文件后保存退出,然后在命令行当中执行make指令便可以生成可执行程序,以及该过程产生的中间产物

Linux编译器--gcc和g++使用_第9张图片

说明:gcc/g++携带-c选项时,若不指定输出文件的文件名,则默认输出文件名为xxx.o,所以这里也可以不用指定输出文件名。

3.4 make原理

1.make会在当前目录下找名字为“Makefile”或“”makefile
的文件。
2. 如果找到,它会找文件当中的第一个目标文件,在上面的例子中,它会找到mytest这个文件,并把这个文件作为最终的目标文件。
3. 如果mytest文件不存在,或是mytest所依赖的后面的test.o文件和main.o文件的文件修改时间比mytest文件新,那么它就会执行后面的依赖方法来生成mytest文件。
4. 如果mytest所依赖的test.o文件不存在,那么make会在Makefile文件中寻找目标为test.o文件的依赖关系,如果找到则再根据其依赖方法生成test.o文件(类似于堆栈的过程)。
5. 当然,你的test.c文件和main.c文件是存在的,于是make会生成test.o文件和main.o文件,然后再用test.o文件和main.o文件生成最终的mytest文件。
6. make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
7. 在寻找的过程中,如果出现错误,例如最后被依赖的文件找不到,那么make就会直接退出,并报错。

makefile是如何得知,我的可执行程序是最新的?
根据文件的最近修改时间来的!

stat mytest

Linux编译器--gcc和g++使用_第10张图片

Access:表示文件最近被访问的时间(修改也算访问)
Modify:修改时间(是指文件内容的改变,改变内容其实也会改变文件的属性(大小))
Change:修改时间(是指文件属性的改变,比如权限设置)

源文件的可执行文件一定比新文件的时间要早,make根据这个时间来判断文件是不是最新的,是否需要执行该文件。

3.5 项目清理

在我们每次重新生成可执行程序前,都应该将上一次生成可执行程序时生成的一系列文件进行清理,但是如果我们每次都手动执行一系列指令进行清理工作的话,未免有些麻烦,因为每次清理时执行的都是相同的清理指令,这时我们可以将项目清理的指令也加入到Makefile文件当中。
想clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,但我们可以显示要make执行。
Linux编译器--gcc和g++使用_第11张图片
:一般将这种clean的目标文件设置为伪目标,用.PHONY修饰,伪目标的特性是:总是被执行。

四、linux下的第一个小程序-进度条

4.1 行缓冲区的概念

首先,我们来感受一下行缓冲区的存在,在Linux当中以下代码的运行结果是什么样的?
Linux编译器--gcc和g++使用_第12张图片
对于此代码,大家应该都没问题,当然是先输出字符串hello 然后休眠1秒之后结束运行。那么对于以下代码呢?

Linux编译器--gcc和g++使用_第13张图片
可以看到代码中仅仅删除了字符串后面的‘\n’,那么代码的运行结果还与之前相同吗?答案是否定的,该代码的运行结果是:先休眠3秒,然后打印字符串hello 之后结束运行。该现象就证明了行缓冲区的存在。

显示器对应的是行刷新,即当缓冲区中遇到‘\n’或是缓冲区被写满才会被打印出来,而在第二份代码中并没有‘\n’,所以字符串hello world先被写到缓冲区当中去了,然后休眠3秒后,直到程序运行结束时才将hello打印到显示器当中。
我们在代码中添加fflush(stdout)后就能够实现立马刷新,直接显示hello
Linux编译器--gcc和g++使用_第14张图片
这次的结果就是马上输出hello

4.2 \r和\n

\r:回车,使光标回到本行行首
\n:换行,使光标下移一格
而我们键盘上的Enter键实际上就等价于\n+\r
既然是\r是使光标回到本行行首,那么如果我们像显示器上写了一个数之后再让光标回到本行行首,然后再写一个数,不就相当于将前面一个数字覆盖了吗?
但这里有一个问题:不使用‘\n’进行换行怎么将缓冲区当中数据打印出来?
这里我们可以使用ffush函数,该函数可以刷新缓冲区,即将缓冲区当中的数据刷新到显示器当中。
对此我们可以编写一个倒计时的程序。
Linux编译器--gcc和g++使用_第15张图片

4.3 进度条代码

知道了\r这个概念我们就可以实现一个简单的进度条了。
Linux编译器--gcc和g++使用_第16张图片
在这里插入图片描述

你可能感兴趣的:(linux,服务器,运维)