格式: gcc [选项] 要编译的文件 [选项] [目标文件]
,gcc / g++
安装: sudo yum install -y gcc-c++
。安装后的编译器默认的版本是较低的,我们可以使用选项-std=c99
(即使用c99标准),-std=c++11
(即使用c++11的标准)来进行版本提升。使用-o
选项,可以将编译生成的可执行重命名。最后使用./可执行
,来运行程序。如下:
Linux中一个文件要真正的执行:1. 有x
权限;2. 就是一个可执行文件。 很明显文件test.exe
满足上述条件!
程序的翻译过程,可以说是生成二进制方案的过程,具体可以细分为以下几步:
预处理功能主要包括宏定义,文件包含,条件编译,去注释等。 预处理指令是以#号开头的代码行。
实例: gcc -E test.c -o test.i
。选项"-E"
,该选项的作用是让gcc
从现在开始进行程序的翻译,预处理完成就停下! 选项"-o"
是指目标文件,".i"
文件为已经过预处理的C原始程序。
在这个阶段中,gcc
首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc
把c语言代码翻译成汇编语言。
实例: gcc -S test.i -o test.s
。用户可以使用"-S"
选项来进行查看,该选项的作用是让gcc
从现在开始进行程序的翻译,编译完成就停下! 可以从test.c
开始编译,也可以从test.i
开始。
汇编阶段是把编译阶段生成的".s"
文件转成目标文件(即将汇编语言编译成为,二进制目标文件)
实例: gcc -c test.s -o test.o
。读者在此可使用选项"-c"
,该选项的作用是让gcc
从现在开始进行程序的翻译,汇编完成就停下!。就可看到汇编代码已转化为".o"
的二进制目标代码了。
实例: gcc hello.o –o hello
。在成功编译之后,就进入了链接阶段,最后形成可执行程序。
那么为什么会有这些过程呢?
这就要说到编程语言的历史了。最初的计算机只能处理二进制,二进制是处理器的“母语”。这叫“机器语言”或者“机器码”。在计算机早期阶段,必须用机器码写程序(即二进制编程)(前段时间很火的《三体1》中,早期叶文洁当时就是使用的二进制编程,还用到了打孔带,上面记录的就是二进制信息)。
在1940-1950年代,程序员开发一种新语言(汇编语言),它为每个操作码分配一个简单名字,叫“助记符”,助记符后面紧跟数据,形成完整指令。当然,计算机并不认识助记符,它只认得二进制码,然后就有了汇编的编译器,它可以读懂文字指令,自动转换成二进制指令,这种程序叫“汇编器”
因为计算机只认识二进制。以c语言为例,所以我们要先将c语言代码转化为汇编代码,然后再转为二进制目标文件。 那为什么不直接将c语言转为二进制目标文件呢?因为前人已经写好了汇编的编译器,我们只需站在巨人的肩膀上,再将高级语言转为较简单的汇编代码即可。
那么如何形成第一个汇编写的汇编编译器呢?
我们的C程序中,并没有定义"printf"
的函数实现,且在预编译中包含的"stdio.h"
中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实"printf"
函数的呢?
最后的答案是:系统把这些函数实现都被做到名为 libc.so.6
的库文件中去了,在没有特别指定时,gcc
会到系统默认的搜索路径 /usr/lib
下进行查找,也就是链接到 libc.so.6
库函数中去,这样就能实现函数"printf"
了,而这也就是链接的作用。
函数库一般分为静态库和动态库两种。
".a"
".so"
,如前面所述的 libc.so.6
就是动态库。gcc
在编译时默认使用动态库。完成了链接之后,gcc
就可以生成可执行文件,如下所示。 gcc test.o –o test
gcc
默认生成的二进制程序,是动态链接的,这点可以通过 file
命令验证。验证:
file filename
指令,查看链接属性:动态库 && 动态链接的优缺点:1. 不能丢失。共享动态库,但是一旦动态库缺失,所有的动态链接这个库的程序,都无法执行! 2. 节省资源。
静态库 && 静态链接的优缺点:1. 一旦形成,与库无关。在编译的时候,把库中的方法,拷贝到我自己的可执行程序中,此时程序将不再关心任何的库; 2. 浪费资源,占用空间大。
程序的发布方式有两种,debug
模式和release
模式。Linux gcc/g++
出来的二进制程序,默认是release
模式。 要使用gdb
调试,必须在源代码生成二进制程序的时候,加上 -g
选项,此时编译器形成可执行程序的时候,会给可执行程序添加调试信息。 我们也可以发现可调式的可执行程序比普通的程序占用内存大。
调试信息大致如下:
gdb binFile
退出: ctrl + d
或 quit
调试命令:
makefile
,从一个侧面说明了一个人是否具备完成大型工程的能力;makefile
定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作;makefile
带来的好处就是——“自动化编译”,一旦写好,只需要一个make
命令,整个工程完全自动编译,极大的提高了软件开发的效率;make
是一个命令工具,是一个解释makefile
中指令的命令工具,一般来说,大多数的IDE
都有这个命令,比如:Delphi
的make
,Visual C++
的nmake
,Linux
下GNU
的make
。可见,makefile
都成为了一种在工程方面的编译方法;make
是一条命令,makefile
是一个文件,两个搭配使用,完成项目自动化构建。make
是如何工作的,在默认的方式下,也就是我们只输入make
命令。那么,
make
会在当前目录下找名字叫"Makefile"
或"makefile"
的文件。target
),在上面的例子中,他会找到"code.exe"
这个文件,并把这个文件作为最终的目标文件。code.exe
文件不存在,或是code.exe
所依赖的后面的code.o
文件的文件修改时间要比code.exe
这个文件新(可以用 touch
测试 - 前面讲过touch
可以修改文件最近一次被修改的时间),那么,他就会执行后面所定义的命令来生成code.exe
这个文件。code.exe
所依赖的code.o
文件不存在,那么make
会在当前文件中找目标为code.o
文件的依赖性,如果找到则再根据那一个规则生成code.o
文件。(这有点像一个堆栈的过程)make
会生成 code.o
文件,然后再用 code.o
文件声明make
的终极任务,也就是执行文件code.exe
了。make
的依赖性,make
会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。make
就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make
根本不理。make
只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。makefile / make
会自动根据文件中的依赖关系,进行自动推导,帮助我们执行所有的相关的依赖方法。
makefile
文件中,保存了编译器和链接器的参数选项,并且描述了所有源文件之间的关系。make
程序会读取makefile文
件中的数据,然后根据规则调用编译器,汇编器,链接器产生最后的输出。Makefile
里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。1. 显式规则说明了,如何生成一个或多个目标文件。2. make
有自动推导的功能,所以 隐晦规则可以让我们比较粗糙地简略地书写makefile
,比如源文件与目标文件之间的时间关系判断之类。3. 在makefile
中可以定义变量,当makefile
被执行时,其中的变量都会被扩展到相应的引用位置上。4. 通常使用 $(var)
表示引用变量文件指示。包含在一个makefile
中引用另一个makefile
,类似C语言中的include
。5. 注释,makefile
中可以 使用 #
在行首表示行注释。make
命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”
的文件clean
这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make
执行。 即命令——“make clean”
,以此来清除所有的目标文件,以便重编译。clean
的目标文件,我们将它设置为伪目标,用.PHONY
修饰,伪目标的特性是,总是被执行的。 makefile
中的伪对象表示对象名称并不代表真正的文件名,与实际存在的同名文件没有相互关系,因此伪对象不管同名目标文件是否存在都会执行对应的生成指令。伪对象的作用有两个,1. 使目标对象无论如何都要重新生成。2. 并不生成目标文件,而是为了执行一些指令.
于是我们便可使用vim
编写如上内容。里面$
相当于取值操作,@
最终会被替换目标文件,而^
会被替换为依赖方法。
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=12k49c75rav0l