目录
一:GCC
1:简历
2:编译过程
3:编译顺序
A:预处理
B:编译
C:汇编
D:链接
4:编译选项
5:静/动态库
A: 前言
B: 库
C:静态库
D:动态库
6:练习
A: 目录结构
B: 目录结构
二:makefile
1:makefile变量
A:make变量预定义变量
B:make自动化变量
2:3个等号
A:递归定义方式。
B:直接定义方式 :=
C:条件定义法 ?=
四:调式工具GDB
1:启动GDB的2中方式
A: GDB+调式文件的名字
B: 进入gdb
2:调试命令
编辑c语言或者c++的工具
GCC (GNU Compiler Collection),即 GNU 编译器套装,是一套由 GNU 开发的编程语言编译器。它是一套以 GPL 及 LGPL 许可证所发行的自由软件,也是 GNU 计划的关键部分还是自由的类 UNIX 及苹果计算机 Mac OS X 操作系统的标准编译器。GCC(特别是其中的C 语言编译器)也常被认为是跨平台编译器的事实标准。
Linux 系统下的 GCC 编译器实际上是 GNU 编译工具链中的一款软件,可以用它来调用其他不同的工具进行诸如预处理、编译、汇编和链接这样的工作。GCC 不仅功能强大、性能优越,其执行效率比一般的编译器相比要高 20%~30%,而且由于其是 GNU 项目之一,是开源的软件,我们可以直接从网上免费地下载安装它,是名副其实的免费大餐。
cpu只能读取二进制文件, 所以我们需要把c或者c++使用GCC编译为二进制文件
格式: gcc [选项] [要编译的文件] [选项] [目标文件]
gcc hello.c - o hello
结论 :
如果我们直接使用 gcc hello.c 会默认为我们生成一个a.out可执行文件
如果我们直接使用 gcc -c hello.c 会默认生成一个以本文件为名字的.o文件 (hello.o)
如果我们直接使用 gcc -c hello.c -o 文件名 会默认生成一个指定的文件名(文件名由我们指定)
编译顺序 :
把原本的xxx.c文件通过预处理器 cpp变为xxx.i
gcc会对.c文件默认进行预处理操作; GCC在第一个阶段会调用预处理器 cpp 来对 C 源程序进行预处理,所谓的预处理就是解鲁源程序当中所有的预处理指令,那些如#include、 #define、#if等以井号(#)开头的语句就是预处理指令,预处理指令实际上并不是C 语言本身的组成部分,而是为了更好地组织程序所使用的一些“预先处理的”工作,这些工作用一种称为与处理指令的语句来描述,然后用预处理器来解释,这些工作包括我们熟悉的诸如文件包含、宏定义、条件编译等。
把xxx.i文件通过编译器(ccl)编译为汇编语言.s
经过预处理之后生成的.i 文件依然是一个文本文件,不能被处理器直接解释,我们需要进一步编译。接下来的编译阶段是 4 个阶段中最为复杂的阶段,它包括词法和语法的分析,最终生成对应硬件平台的汇编语言(不同的处理器有不同的汇编格式),具体生成什么平台的汇编文件取决于所采用的编译器,如果用的是 GCC,那么将会生成x86 格式的汇编文件,如果用的是针对 ARM平台的交叉编译器,那么将会生成 ARM 格式的汇编文件。
把xxx.s文件通过汇编器 as 编译为二进制文件.o
编译器 gcc 将会调用汇编器 as 将汇编源程序翻译成为重定位文件。汇编指令跟处理器直接运行的二进制指令流之间基本是一一对应的关系,该段只需要将.s 文件里面的汇编翻译成指令即可
这个件是一个 ELF 格式的可重定位 (relocatable) 文件。所谓的可重定位,指的是该文件虽然已经包含可以让处理器直接运行的指令流,但是程序中所有的全局符号尚未定位。所谓的全局符号,就是指函数和全局变量,函数和全局变量默认情况下是可以被外部文件引用的,由于定义和调用可以出现在不同的文件当中,因此它们在编译的过程中需要确定其入口地址,如 a.c 文件里面定义了一个函数 func0,b.c 文件里面调用了该函数,那么在完成第 3 阶段汇编之后,b.o 文件里面的函数 func0的地址将是 0,显然这是不能运行的,必须找到 a.c 文件里面函数 func0的确切的入口地址,然后将 b.c 中的“全局符号”func 重新定位为这个地址,程序才能正确运行。因此,接下来需要进行第 4 个阶段: 链接。
经过汇编之后的可重定位的文件不能直接运行,因为还有两个很重要的工没完成,首先是重定位,其次是合并相同权限的段, 标准的c库和内置库是如此的基本, 因此-lc -lgcc 是默认的可以省略
小结:
1)输入文件的后缀名和选项共同决定gcc到底执行那些操作。
2)在编译过程中,除非使用了-E、-S、-c选项(或者编译出错阻止了完整的编译过程)
否则最后的步骤都是链接。
直接生成可执行文件:
-w 不生成任何警告信息
-D 相当于程序中的define
头文件默认在 : 默认路径--编译器中的include中
include
<>使用这个引用, 他会在默认的路径include中查找 , 以为add.h 是我们自己写的默认路径中没有,所以直接错误
解决方法: A:把我们写的add.h加入到默认头文件路径include中去
B:gcc -c hello.c -o hello.o -I后面加头文件的路径
C: 变为" "---直接在当前目录下找头文件
include "add.h"
""-------会在当前目录下查找头文件
函数库的概念 :
printf函数在库中; 库的默认路径为:编译器中的lib目录
库可以指定:
-L选项指定的目录 -l选项指定库(没有空格)
-labc 意思 找libabc.so或者-labc 意思 找libabc.a的库
我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实现“printf”函数的呢?
系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib64”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用
查看可执行文件所依赖的库文件: ldd hello.exe (ldd后面必须跟的是可执行文件)
查看当前使用的头文件 : ls /usr/include ------头文件默认的储存地址
一般链接的过程,是有两种方式的:
所以函数库也就分为静态库和动态库两种:
静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a”
动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件。
gcc默认生成的二进制程序,是动态链接的,这点可以通过 file 命令验证
file hello.exe
也可以改为静态库(加上--static) : #不加static的话默认生成运态库
gcc -static hello.c -o hello
编静态库;
ar -r [lib自定义库名.a] [.o] [.o].....
链接成可执行文件:
gcc [.c] [.a] -o [自定义输出文件名]
gcc [.c] -o [自定义输出文件名] -l[库名] -L[库所在路径]
方法2:
第一程序链接时指定链接库的位置,就是使用-wl,-rpath=
编译二进制.o文件
gcc -c -fpic [.c/.cpp][.c/.cpp]...
编库
gcc -shared [.o][.o]...-o [lib自定义库名.so]
连接动态库到可执行文件
gcc [.c/.cpp] -0 [自定义可执行文件名] -l[库名] -L[库路径] -wl,-rpath=[库路径]
加入环境变量:
export LD_LIBRARY_PATH=/opt/gtk/lib:$LD_LIBRARY_PATH
直接生成:
创建静态库:
首先创建静态库, 然后在链接为可执行文件
创建动态库:
obj=demo.o add.o
exe:$(obj)
$(CC) $^ -o $@
%.o:%.c
$(CC) -c $^ -o $@
add.o:add.h
clean:
rm *.o cs-f //-f 强行删除
改进makefile:
上面的add.o依赖与add.h 需要打开这个文件, 来确定头文件. 我们来改进这个
obj=demo.o add.o
text:$(obj)
$(CC) $^ -o $@
# 需要判断是否存在依赖文件
# .demo.o.d .add.o.d
dep:=$(foreach f,$(obj),.$(f).d)
dep:=$(wildcard $(dep))
#把依赖文件包含进来
ifneq ($(dep),)
include $(dep)
endif
%.o:%.c
$(CC) -Wp,-MD,[email protected] -c $< -o $@
clean:
rm *.o cs -f
distclean:
rm $(dep) *.o cs -f
-Wp,-MD, -------生成相应的依赖文件 .--表示他是一个隐藏文件 ls -a 查看隐藏文件
foreach-----把obj中的每个成员(demo.o add.o)赋值改f, 然后在替换
wildcard-----展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。假如不存在任何符合此模式的文件,函数会忽略模式字符并返回空。
ifneq ($(dep),)
include $(dep)
endif# 当修改.h文件之后,判断执行这个include $(dep_files), 表示如果变量 dep 不为空,则包含 dep 文件。这个语句通常用于自动化构建中,用来检查依赖关系并自动更新相关文件。
创建静态库:
首先创建静态库, 然后在链接为可执行文件
创建动态库: 因为上面已经加上了环境变量,所以这次我们不用加环境变量
前言:
makefile的本质就是对gcc的优化, 避免了每次编译都对文件中的全部文件进行编译. makefile只会对进行改变的文件进行重新编译, 没有动的文件不编译节约了时间
当我们要编译成千上万个源程序文件时,光靠手工地使用 GCC 工具来达到目的也许就会很没有效率,我们需要一款能够帮助我们自动检查文件的更新情况,自动进行编译的软件GNU make (工程管理器 make 在不同环境有很多版本分支,比如 Qt 下的 qmake,Windows下的nmake 等,下面提到的 make 指的是 Linux 下的 GNUmake)就是这样的一款软件。
而Makefile,是 make 的配置文件,用来配置运行 make 时的一些相关细节,比如指定编一个工程项目不管是简单还是复杂,每一个源代码子译选项、指定编译环境等。一般而言,目录都会有一个Makefile 来管理,然后一般有个所谓的顶层 Makefile 来统一管理所有的子目Makefile。
工作原理:
make 的工作目的很清楚: 编译那些需要编译的文件,那么究竟哪些文件需要编译呢?
这个原理也非常简单 : 根据文件的时间戳来进行判断。每个文件都会记录其最改时间,我们只需要对比源文件及其生成的目标文件时间戳,就可以判断它们的新旧关系,从而决定要不编译。比方说我们刚刚修改了 x.c 这个文件,那么它的间戳将会被更新为当前最新的系统时间,这样make通对比就可以知道 x.c 比 x.o 要新,因此在需要使用x.o就会自动重新编译 x.o,这样又会导致 x.o 的时间戳比image 要新,于是 image 也会被自动重新编译,这种递关系会在每一层目标-依赖之间传递
shell命令执行2个条件 :依赖文件比目标文件新,或是 目标文件还没生成
makefile格式:
目标:依赖
shell命令
//shell命令前面必须是Tab键,否则不能识别shell命令
exe:main.o add.o
gcc main.o add.o -o exe
main.o:main.c
gcc -c main.c -o main.o
add.o:add.h
add.o:add.c
gcc -c add.c -o add.o
终极目标就是指的是执行make是默认产生的那个文件----exe文件
第一条规则中的目标将被确立为最终的目标
第一次执行make命令, 全部编译, 下次执行的时候,那个改变执行那个
流程:
(1)找到由终极目标构成的一套规则(第1 行和第2行)。
(2)如果终极目标及其依赖列表都存在,则判断它们的时间戳关系,只要目标比任何一个依赖文件旧,就会执行其下面的 Shell 命令。
(3)如果有任何一个依赖文件不存在,或者该依赖文件比该依赖文件的依赖文件要旧则需要执行以该依赖文件为目标的规则的 Shell 命令。
(4)如果依赖文件都存在并且都最新,但是目标不存在,则执行其下面的 Shell 命令。
寻找:
make命令默认在当前目录下寻找名字为makefile或者Makefile的工程文件,当名字不为这两者之一时,可以使用如下方法指定:
make -f 文件名
伪目标:
Makefile中把那些没有任何依赖只有执行动作的目标称为“伪目标”
.PHONY : clean
clean:
rm -f sc *.o
#.PHONY---将clean目标声明为伪目标
作用: 避免命令与目录下的文件名重复---------如果当前目录下没有名为“clean”的文件,则rm指令会被执行。如果有的话,由于clean没有依赖文件,所以目标被认为是最新的而不去执行rm指令。
在Makefile中加入伪目标声明可防止这种情况发生
所以现在上面的makefile命令可以改为:
obj=main.o add.o
exe:$(obj)
$(CC) $^ -o $@
main.o:main.c
$(CC) -c $^ -o $@
add.o:add.h
add.o:add.c
$(CC) -c $< -o $@
//========================或者================================
obj=main.o add.o
exe:$(obj)
$(CC) $^ -o $@
%.o:%.c
$(CC) -c $^ -o $@
add.o:add.h
clean:
rm *.o 文件名-f
2个相同的目标,一个有规则,一个没有规则; 他们会合并在一起
A =Ilove $(B)
B= China
此处,在变量 B 出现之前,变量 A 的定义包含了对变量 B 的引用,由于 A 的定方式是所谓的“递归”定义方式,因此当出现S(B)时会对全文件进行搜索,找到 B 的值并代进 A
A =Ilove $(B)
B= China
递归定义的变量有以下两个缺点。第一,使用此风格的变量定义,可能会由于出现变量中,结果变量A的值是“Ilove China”。的递归定义而导致make陷入到无限的变量展开过程中,最终使make执行失败。
B= China
A:=l love $(B)
此处,定义A 时用的是所谓的“直接”定义方式,即如果其定义里出现有对其他变量的A;=I只会在其前面的语句进行搜寻(不包含自己所在的那一行),而不是搜寻整个文件,因此,如果此处将变量 A 和变量 B 的定义交换一个位置
有时我们需要先判断一个变量是否已经定义了,如果已经定义了则不做操作,如果没有定义方式。定义则再来定义它的值,这时最方便的方法就是采用所谓的条件定义方式:
A=apple
A?=asddasd
在A?=的前面对于A已经定义了, 所以A这个时候等于apple; 否则A等于asddasd
GDB是GNU发布的一款功能强大的程序调试工具。GDB主要完成下面三个方面的功能
1、启动被调试程序
2、让被调试的程序在指定的位置停住3、当程序被停住时,可以检查程序状态
如变量值)
因为是调式所以在编译的时候加上 -g 编译结果加入调式信息
file 调式程序名