GCC编译器、gdb调试和Makefile编写

一、GCC编译器

1、GCC的执行过程

在这里插入图片描述

  • 在Linux写生成最终的文件是ELF文件(可执行文件)
    分解由预处理、编译、汇编和链接。
    执行过程:.c文件 —> .i文件 —> .s文件 —> .o文件 —> .elf文件

2 、逐步了解GCC的过程

#预处理,解释源程序当中所有的预处理指令;预处理指令实际不是C语言本身的组成部分
$ gcc hello.c  -o hello.i  -E  #预处理步骤
#编译,词法分析和语法分析,最终生成对应硬件平台的汇编语言;简单的说将.i文件编程.s文件(汇编)  
$ gcc hello.i  -o hello.s -S    #编译
#汇编: as将汇编程序翻译成可重定向文件,可重定向是全局符号尚未定位; 全局符号:函数和全局变量
$ gcc hello.s  -o hello.o -c   #汇编
#链接:重定向和合并相同权限的段,链接系统的C库
$ gcc hello.o  -o hello -lc -lgcc  #链接标准C库和链接gcc库

3、GCC的中间产物 *.o 文件

  • hello.o的ELF格式
    在这里插入图片描述
  • 对应各个段的解释:
    .text段 存放运行代码
    .data段 已经初始化的全局变量和静态局部变量
    .rodata段 存放程序中所有的常量等
    例如.text段和.rodata段只具有只读权限,所以后期会合并在一起

4、GCC的各个参数

选项 作用 示例
-o < file > 指定输出文件名 gcc a.c -o a
-E 输出预处理后的代码文件 gcc a.c -o a.i -E
-S 输出编译后的汇编代码文件 gcc a.c -o a.s -S
-c 输出链接后的可重定向文件 gcc a.c -o a.o -c
-g 在编译结果中加入调试信息 gcc a.c -o a -g
-I< path > 指定头文件路径 gcc a.c -o a -I./inc
-L< path > 指定库文件路径 gcc a.c -o a -L./lib
-O< rank > 指定优化等级 gcc a.c -o a -O2
-static 使用静态链接 gcc a.c -o a lxxx -static
-Wall 打开所有的警告 gcc a.c -o a -Wall
  • 可用的优化等级有4个,分别是O0、O1、O2和O3。
    优化等级越高,编译速度越慢,相对而言程序运行速度越快,调试难度越大。
    其中O0是关闭所有优化项目。
  • 链接库文件xxx时,如果系统中同时出现存在其对应的静态库和动态库,使用此选项可以使得程序链接静态库,使程序编译之后不依赖该库文件。

二、gdb调试

  • 再终端输入gdb打开调试
$ gdb
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) 

  • gdb的指令说明
    在这里插入图片描述

三、Makefile编写

1、GCC的4部曲

#GCC的编译四部曲及运行过程
#目标文件:依赖文件  的规则
hello:hello.o   #链接
	gcc hello.o -o hello -lc -lgcc
hello.o:hello.s #汇编
	gcc -c hello.s -o hello.o
hello.s:hello.i #编译
	gcc -S hello.i -o hello.s
hello.i:hello.c #预处理
	gcc -E hello.c -o hello.i  

.PHONY: clean   #伪目标的声明
clean:
	rm -rf hello.i hello.s
run:
	./hello

# 1、写法,目标:依赖列表 (依赖列表可以没有);执行命令行,即echo的一行前面必须是tab键,不能是空格
# 2、过程,先判断目标的依赖项是否存在;再判断目标(funy)是否已经存在,如果存在目标直接退出,若不存在执行shell命令
# 3、执行顺序是由下而上的

2、变量的理解

#makefile的 =、 :=和?=的区别
# = 最后赋值  最后变量赋值成什么,就是啥
VAR=A   
VARB=$(VAR) B
VAR = AA         #最终结果

# := 当前赋值 直接赋值
VIR_A := A  
VIR_B :=$(VIR_A) B
VIR_A = AA      #最终结果


# ?= 表示如果没有被赋值,则给予等号后面的值
P0 = 张三
PP ?= new_value
P0 = 李四      #最终结果

# += 表示追加(符合语句) 可以理解成字符串相连 没加一次都有空格
str =C
str +=语言
str1 = C语言    #最终结果

all:
	echo $(VARB)   #最后指定的值输出的值为 AA B  =号的理解
	
	echo $(VIR_B)  #输出是A B   即当前的值
	
	echo $(P0)     #?=赋值现象
	echo $(PP)
	
	echo $(str)
	echo $(str1)

3、伪命令的编写和链接文件

CC = gcc
SRC = 1.c 2.c 3.c main.c
TARGET = main

TARGET:
	@$(CC) $(SRC) -o $(TARGET) -I ../
.PYONY:   #伪目标 clean   (当有文件clean是 clean指令也可以执行)

clean:
	@rm $(TARGET)    #@加在最前面此行命令不显示
run:
	@./main

4、系统默认函数的使用

#三种常见的函数wildcard、notdir、patsubst和addprefix
#wildcard 扩展通配符
#notdir   去除路径通配符
#patsubst 替换通配符
#addprefix 添加前缀的匹配符
#请记住,这些函数只做字符串处理,并没有处理到文件(实体)

OBJDIR=obj/
obj0 =$(wildcard *.c  src/*.c)   	 #当前目录的所有.c和new_dir下的所有.c文件  全放在src对象上(字符串)
obj1 =$(notdir $(obj0))				 #将所有的.c文件都去掉路径(将字符串的路径全部去掉)
obj2 =$(addprefix $(OBJDIR),$(obj3)) #将.o字符串前面加上路径
obj3 =$(patsubst %.c,%.o,$(obj1))	 #将所有去掉路径的.c字符串全部转变成对应的.o字符串 (最后保存在obj3中 ,可以供obj2使用)
obj4 =$(obj1:%.c=%.i)       #转化成想要的.i字符串 (这种方法想当于替换通配符 patsubst)
obj5 =$(obj4:%.i=%.S)		#转化成想要的.S字符串
obj6 =$(obj5:%.S=%.o)		#同理

all:
	@echo $(obj0)
	@echo $(obj1)
	@echo $(obj2)
	@echo $(obj3)
	@echo $(obj4)
	@echo $(obj5)
	@echo $(obj6)

#伪目标  加上@后就不显示了
.PHONY:  run 
run:
	@#首先得到多个对应的.o文件实体 
	
	@#想一步将所有.c文件都生成对应的.o文件 使用了这个写法
	@#gcc $(obj0) -c -o $(obj3) -I ../include/    #汇编
	@#不行
	
	@#故,只有一步一步的运行成.o文件
	gcc ./src/1.c -c -o 1.o -I ./inc/
	gcc ./src/2.c -c -o 2.o -I ./inc/
	gcc ./src/3.c -c -o 3.o -I ./inc/
	gcc ./src/main.c -c -o main.o -I ./inc/
	
	@#然后一起链接二进制文件成一个执行文件
	gcc $(obj3) -o main

	@#删除所有的.o文件
	@rm -rf $(obj3)   
	
	#运行
	./main

5、自定义函数和override的使用

  • 自定义函数
#多文件的编译  自定义函数 define ……endef(函数)
.PHONY : test  #伪目标的声明
define fun1   #定义
	@echo "My name is $(0)"
endef
define fun2  #定义
	@echo "My name is $(0), param is $(1)"
endef
test:
	$(call fun1)    #调用需要加上第一个参数call
	$(call fun2, hello Makefile)
  • override的使用
#makefile的override的使用   
#makefile默认在运行时可以附加一个变量赋值


# A = a apple tree
# all:
	# @echo $(A)
#一种写法 make A=zhangsa  结果运行 zhangsa

#第二种写法
# override A="a apple tree"

# all:
	# @echo $(A)
#make A=zhangsan 运行结果 a apple tree   有点像const但不是

#第三个写法
# all:
	# #下面这句话是 -g调试 -O0编译器优化等级 -Wall打开所有警告 cc表示全局变量gcc(Makefile默认的) -o 重新定义目标
	# cc -g -O0 -Wall hello.c -o hee

#第四种写法
#对于追加方式需要说明的是:变量在定义时使用了“override”,则后续对它值进行追加时,也需要使用带有“override”指示符的追加方式。否则对此变量值的追加不会生效。
override ClA += -Wall   #不管加不加其他的调试参数,必须都有-Wall打开所有警告的信息

override ClA += -DBUG  #添加宏引入
#override定义函数时,也照样被覆盖了功能
define CLA_FUN0   #函数不加的override
	echo "I love C!"
endef
override define CLA_FUN0   #函数不加的override
	echo "I love C语言!"
endef

.PTONY: all
all:
	echo $(ClA)
	cc $(ClA) zhangsan.c -o zhangsan
	$(call CLA_FUN0) #函数调用
	@echo "OK"
	

6、通配符的使用 自动变量的通配符的使用

  1. 自动变量的参数
通配符 使用说明
* 匹配0个或者是任意个字符
匹配任意一个字符
[ ] 我们可以指定匹配的字符放在“[]”中
.PHONY:clean
clean:
    rm -rf *.o test
#这个实例可以说明通配符可以使用在规则的命令当中,表示的是任意的以 .o 结尾的文件。
test:*.c
    gcc -o $@ $^
#通配符不仅可以使用在规则的命令中,还可以使用在规则中。用来表示生所有的以 .c 结尾的文件。  
OBJ=*.c
test:$(OBJ)
    gcc -o $@ $^
#要表示的是当前目录下所有的 ".c" 文件,但是我们在使用的时候并没有展开,而是直接识别成了一个文件。文件名是 "*.c"。

7、文件的路径变动

#函数 :	foreach
#$(foreach <var>,<list>,<text>)
#函数功能:循环函数,会把TEST中的数据,赋值到	
#	这个函数的意思是,把参数<list>,中的单词逐一取出放到参数<var>;
#	所指定的变量中,然后再执行<text>,所包含的表达式。
#	每一次<text>,会返回一个字符串,循环过程中,<text>;
#	的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>;
#	所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。

#所以,<var>最好是一个变量名,
#<list>可以是一个表达式,而<text>中一般会使用<var>;

#这个参数来依次枚举<list>中的单词.举个例子:

#   names := a b c d
#   files := $(foreach n,$(names),$(n).o)

#上面的例子中,
#从$(name)中挨个取出单词,依次存到变量 "n"中,
#"$(n).o"每次根据"$(n)"计算出一个值,
#这些值以空格分隔,最后作为foreach函数的返回,所以,$(files)的值是"a.o b.o c.o d.o".

TARGET = main 
CC    := gcc

#.c文件的目录在src .当前目录的文件
DIRS = ./src 
 
#指定头文件路径
CFLAGS = -I /mnt/hgfs/linux/include

#在指定路径,获取到所有的.c文件放入到SRCS变量中
SRCS   =  $(foreach dir,$(DIRS),$(wildcard $(dir)/*.c))

#把所有从 $(SRCS) 中搜索到的 .c 依赖文件 都替换为 .o 目标文件 放入到OBJ的列表当中
OBJ    =  $(patsubst %.c,%.o,$(SRCS)) 
RMRF  := rm -rf

$(TARGET):$(OBJ)
	$(CC) $^ -o $@

.PHONY: 
clrearall:
	$(RMRF) $(OBJ) $(TARGET)
clrear:
	$(RMRF) $(OBJ) 	

猜你喜欢

转载自blog.csdn.net/weixin_44763594/article/details/120484045