第五课.Linux开发基础


虽然已经有很多优秀的IDE可以化简开发任务,但在面对从底层到外壳的系统构建任务时,基于Linux的传统开发模式可以让过程更透明,让开发者能够彻底把控开发的每一步,所以了解GCC,make,GDB三个工具是必要的

GCC用法参考

GCC是GNU开发的编程语言套件(GNU Compiler Collection),在默认情况下ubuntu不会提供C/C++的编译环境,ubuntu提供了build-essential包可以一次性把相关软件安装好:

sudo apt install build-essential
apt depends build-essential #查看哪些包被build-essential依赖

一个C语言程序的运行准备工作:
fig1源程序通过GCC编译为汇编程序,汇编程序进一步编译到输出文件(Linux下为*.o,Windows下为*.obj),输出文件通过链接器连接得到可执行文件,加载到内存运行;


当文件过多时,一个一个进行gcc编译很麻烦,所以会借助makefile构建工程,帮助开发者组织程序


现在已经新建了一个C程序hello.c:
fig2下一步借助工具gcc进行编译链接得到可执行文件a.out:

gcc hello.c
./a.out #执行a.out文件

GCC的常用用法

通过对常用用法的举例,实际体会文件运行的过程;
1.源程序预处理
依然是对hello.c文件进行编译,通过-E参数进行预处理,-o参数指定输出文件名:

gcc -E -o hello.cpp hello.c

借助vi打开hello.cpp发现:
fig3hello.c中的内容在预处理文件的最后,前面都是新载入的预处理语句;
2.编译到汇编文件
将预处理后的hello.cpp编译到汇编文件hello.s:

gcc -x cpp-output -S -o hello.s hello.cpp

fig4如果要直接将C源程序编译到汇编文件dihello.s则可以:

gcc -S -o dihello.s hello.c

3.将汇编程序编译到机器码
现在将汇编程序编译到机器码:

gcc -x assembler -c hello.s -o hello.o
#直接利用源程序编译到机器码
gcc -c hello.c -o hello.o

用vi打开机器码观察到:
fig5虽然已经是机器码,但依然不能执行,因为还没有经过链接,所以机器依然不知道这个机器码文件想要发出什么指令;
4.链接生成可执行文件
通过gcc可以直接自动链接到库,生成可执行文件:

gcc -o hello hello.o
#也可以直接从源程序到可执行文件
gcc -o hello hello.c

#执行
./hello

make与Makefile

make是一个命令工具,是一个解释Makefile中指令的工具,一般来说,大多数IDE都有这个命令,比如:Linux下GNU的make,Visual C++的nmake;
make命令执行时,需要一个Makefile文件,用以告诉make命令需要如何编译和链接程序;
Makefile被称为工程文件,每个工程都有工程文件,工程文件的作用:

  • 如果这个工程没有编译过,所有的C文件都将被编译并链接;
  • 如果这个工程只是某几个C文件被修改,则只需编译被修改的C文件;
  • 如果头文件被修改了,则只需编译引用到该头文件的C文件;

可以看出,基于make,大幅度节约了构建工程的时间;

Makefile的规则

makefile的格式为:

<target>:<prerequisites>
[tab]<commands> #任意的shell命令

makefile是shell脚本的进一步封装,专用于工程构建;
makefile如何工作
1.make会在当前目录下找名字叫"Makefile"或"makefile"的文件;
2.如果make命令运行时没有指定目标,默认会执行Makefile文件的第一个目标,第一个目标习惯写为all;
3.make会一层一层找目标的依赖关系;
4.目标的前置依赖都执行完毕后,执行该目标下的<commands>;
假设当前目录下有几个头文件.h,几个C源文件.c,一个文件makefile,一个README.md,一个LICENSE,在当前目录执行make能够自动对文件编译并链接再输出可执行文件;
目标target
一个目标构成一条规则,目标通常是文件名,指明make命令要构建的对象,目标可以是一个文件名,也可以是多个文件名,之间用空格分隔;
目标还可以是某个操作的名字,这称为"伪目标"(phony target),比如clean,如果当前目录中,正好有一个文件叫clean,那么这个目标不会执行。因为make发现clean文件已经存在,就认为没有必要重新构建,从而不会执行clean"伪目标",为了避免这种情况,可以明确声明clean是"伪目标":".PHONY:clean"
前置条件prerequisites
前置条件通常是一组文件名,之间用空格分隔,前置条件决定了目标是否需要重新构建:只要有一个前置文件不存在,或者有过更新(根据时间戳判断),"目标"就需要重新构建
命令commands
命令表示如何更新目标文件,由一行或多行shell命令组成,它是构建目标的具体指令,它的运行结果通常就是生成目标文件;
每行命令之前必须有一个tab键,如果想用其他键,可以用内置变量.RECIPEPREFIX声明。用.RECIPEPREFIX指定大于号>替代tab键:

.RECIPEPREFIX = >

需要注意的是,每行命令在一个单独的shell中执行,这些shell之间没有继承关系,即孤立的命令;


这是文件的依赖关系,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中


Makefile的语法

1.makefile中用#表示注释;
2.正常情况下,make会打印每条命令,然后再执行,这叫回声(echoing),在命令前加上@,可以关闭回声;
3.通配符(wildcard)用来指定一组符合条件的文件名,makefile的通配符与Bash一致,主要有*?等,比如*.o表示所有后缀名为o的文件;
模式匹配
make命令允许对文件名进行正则运算匹配,主要用到的匹配符号是%,假设当前目录下有f1.c和f2.c两个文件,需要将其编译为输出文件,则可简写为%.c:%.o
基于通配符%,可以将大量同类型文件使用一条规则完成构建;
变量和赋值
1.使用等号自定义变量,调用时需写在$()中:

txt = Hello
echo $(txt)

2.调用shell变量,需要在美元符号前,再加一个美元符号(因为make命令会对美元符号转义):

echo $$HOME

3.变量的值可能指向另一个变量,例如v1 = $(v2)
赋值运算符

VARIABLE = value #执行时扩展
VARIABLE := value #定义时扩展
VARIABLE ?= value #变量为空时才设置值
VARIABLE += value #将值追加到变量的尾部

内置变量Implicit Variables
make命令提供了一系列的内置变量,主要是为了跨平台的兼容;
$(CC)指向当前使用的编译器,$(MAKE)指向当前使用的make工具;
自动变量Automatic Variables

$@ 指代当前目标,即make命令当前构建的目标
$< 指代第一个前置条件
$? 指代比目标更新的所有前置条件,之间用空格分隔
$^ 指代所有前置条件,之间用空格分隔
$(@D)$(@F) 分别指向$@的目录名和文件名
$(<D)$(<F) 分别指向$<的目录名和文件名

判断和循环
Makefile使用Bash语法完成循环和判断:

#判断
ifeq ($(CC),gcc)
	libs=$(libs_for_gcc)
else
	libs=$(normal_libs)
endif

#循环
LIST = one two three
all:
	for i in $(LIST); do
		echo $$i;
	done

函数
makefile也可以使用函数,格式为:

$(function arguments) 
#或者 
${function arguments}

Makefile实例

假设现在有一个Makefile文件:

#cleanall cleanobj cleandiff均不是文件
#如果目录下有例如cleanobj的文件,将不会构建目标cleanobj
#为了避免这一现象,可以用.PHONY声明是伪目标
.PHONY:cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff
	rm program

cleanobj : 
	rm *.o

cleandiff :
	rm *.diff

前面已经提到,make指令依据Makefile的描述去执行工程构建的操作,现在目录下有文件:
fig6如果执行:

make cleanobj

会选择出输出文件*.o,并删除;
如果执行:

make cleanall

根据回声echoing看出:
fig7执行顺序是深度优先搜索,先执行前置条件,再执行目标;
如果只输入指令make,会默认执行第一个目标

调试

原始的调试是在源码中插入printf逐步打印变量状态,后来出现了专用的调试工具GDB,将程序装载到GDB下更方便进行调试

GDB

GDB:GNU project Debugger;在开始调试前,必须用程序中的调试信息编译要调试的程序,GDB才能调试所使用的变量,函数;首先,使用gcc -g编译得到程序,参数-g会附加符号信息用于调试:

gcc -g -o hello hello.c

在shell中,可以使用gdb命令并指定程序名参数运行gdb:

gdb hello
程序装入gdb后可以运行
run

fig8

或者在gdb中,使用file命令调试:

file hello

如果一切正常,程序将执行到结束,如果出错,gdb会中断程序,让开发者检查变量状态;
退出gdb:

quit

设置断点

可以在源码的某一行设置断点,gdb会在遇到断点时中断执行:

gdb hello #将hello.c编译后的程序hello载入gdb
break main #在main函数行设置断点
break 6 #在源码hello.c第6行设置断点
next #单步执行,子函数作为一步
step #单步执行,具体到子函数的每一语句
info break #列出当前设置的所有断点

back trace可以查看函数调用的层级:

bt

fig9

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值