Makefile 与 GCC G++ 入门

Makefile和g++学习笔记

g++部分

学习C和C++的同学应该都知道,gcc是一款跨平台的C/C++编译器,可以在Linux/Windows平台下使用,具有十分强大的功能,结构也十分灵活,并且可以通过不同的前端模块来支持各种语言,如Java、Fortran、Pascal、Modula-3和Ada的编译。许多有名的工程和库都是使用gcc进行编译的,如nginx,libevent等。今天我们重点介绍gcc组件中可以用来编译C++程序的g++组件的使用。

​ g++可以在命令行使用,也可以通过配置IDE的编译环境来调用系统配置的g++环境,大家可以根据需要自行配置。下面是一些博主本人使用g++编译程序的一些经验和总结。

1. g++编译过程

g++在对源程序执行编译工作时,需要经过以下四个步骤:

  1. 预处理:对源程序中的宏定义、包含头文件等进行处理,生成后缀名为.i的文件(使用)

使用格式:

g++ -E hello.cpp -o hello.i

hello.cpp是需要编译的源文件,-o 选项指定输出的文件名。这里使用-E选项编译生成hello.i 文件

  1. 转化为汇编文件:使用-S 选项,可以将预处理之后的.i文件转换为目标机器的汇编代码.S文件

使用格式:

g++ -S hello.i  -o  hello.s

使用该参数可以将.i 文件编译生成.s 文件,输出文件名同样使用-o 选项指定。

  1. 汇编文件->目标文件,即转换为机器代码:使用-c选项

使用格式:

g++ -c hello.s -o hello.o
  1. 链接:将上一步产生的目标文件链接为可执行文件,使用-o参数

使用格式:

g++ hello.o  -o  hello

以上过程是g++工具编译cpp源程序的具体过程,在实际使用时我们可以不用按照流程一步步编译,可以一步到位将源程序编译为可执行文件,只需要使用如下命令:

g++  hello.cpp -o  hello

2.g++常用编译选项

在使用g++工具进行编译时,我们可以附加一些编译选项让编译更加智能,从而方便我们查看编译错误和警告。g++提供了许多有用的编译选项,下面总结一些常用选项:

-o FILE: 指定输出文件名,在编译为目标代码时,这一选项不是必须的。如果FILE没有指定,缺省文件名是a.out

-c: 只编译生成目标文件,不链接

-m486: 针对 486 进行代码优化

-Wall: 允许发出gcc能提供的所有有用的警告,也可以用-W(warning)来标记指定的警告

使用格式:

1 g++ -Wall hello.cpp -o hello

-Werror: 把所有警告转换为错误,以在警告发生时中止编译过程;

-v: 显示在编译过程的每一步中用到的命令 ;

-static: 链接静态库,即执行静态链接,g++默认是链接动态库,如果需要链接静态库需要使用本选项进行指定;

-g: 在可执行程序中包含标准调试信息, 使用该选项生成的可执行文件可以用gdb工具进行调试;

-w: 关闭所有警告,建议不要使用该选项

-shared: 生成共享目标文件。通常用在建立共享库时;

-On: 这是一个优化选项,如果在编译时指定该选项,则编译器会根据n的值(n取0到3之间)对代码进行不同程度的优化,其中-O0 表示不优化,n的值越大,优化程度越高;

-L: 库文件依赖选项,该选项用于指定编译的源程序依赖的库文件路径,库文件可以是静态链接库,也可以是动态链接库,linux系统默认的库路径是/usr/lib,如果需要的库文件不在这个路径下就要用-L指定

g++  foo.cpp  -L/home/lib  -lfoo  -o   foo

-I: 该选项用于指定编译程序时依赖的头文件路径,linux平台默认头文件路径在/usr/include下,如果不在该目录下,则编译时需要使用该选项指定头文件所在路径

gcc  foo.cpp  -I/home/include   -o  foo

3.编译动态库

g++除了可以编译源程序生成可执行文件,也可以编译动态链接库,方法如下:

(1) 分步完成

gcc -fPIC -c func.cpp -o func.o 
gcc -shared -o libfunc.so func.o

(2) 一步完成

gcc -fPIC -shared -o libfunc.so func.cpp

静态库的制作

ar -crv libx.a f1.o f2.o #利用 f1.o 和 f2.o 生产静态库文件 libx.a

静态库的链接

gcc hello.c -o hello -static -L . -l x #将hello.c 与 在当前目录下的名为libx.a的静态库文件链接,生产hello可执行文件  编译器会自动补齐 x库文件的前缀和后缀,即 lib 和 .a 与 x 组合成 libx.a 的完整库文件名。

动态库的制作

gcc -fPIC -shared -o libxx.so f1.c f2.c #利用 f1.o 和 f2.o 生产动态库文件 libxx.so

动态库的链接

gcc hello.c -o hello -L . -l xx #将hello.c 与 在当前目录下的名为libxx.so的静态库文件链接,生产hello可执行文件  编译器会自动补齐 xx库文件的前缀和后缀,即 lib 和 .a 与 xx 组合成 libxx.so 的完整库文件名。

但是此时如果直接运行生产的可执行文件还不能正常运行,因为我们的动态库文件没有加入到系统默认的动态库文件夹中,我们需要运行如下命令来查看当前系统默认的动态库文件夹,并将我们的动态库文件加入到该文件夹。

ldd hello #查看名为hello的可执行文件依赖的动态库(也叫共享库)文件夹所在位置
cp libxx.so /lib/x85_64-linux-gnu/   #将动态链接库复制到上面命令输出的文件夹中。

大功告成,程序可以顺利运行了。

编译时进行宏定义(一般用于条件编译)

-D:我们可以使用-D选项来在编译时进行一个宏定义,假设我们在代码中定义了一个条件编译当定义DEBUG时就可以启用,我们就可以在编译时加上如下代码

gcc xxxxx -D 

Makefile部分

Makefile的命令结构如下。

目标:依赖
	@达成目标所需要运行的命令   #@可以让这条命令不显示命令本身,只输出结果,也可以不加。

Makefile可以利用文件的时间来判断文件是否更新。判断依赖和目标文件的最后修改时间,当目标文件比依赖文件旧时,则会重新编译目标文件。

Makefile的通配符

  • %.o:表示一个xx.o文件
  • $@:表示目标文件
  • $<:表示第一个依赖文件
  • $^:表示所有依赖

Makefile的运行

make时后面可以加一个空格,再加上目标的名称,则生成该目标,若没有目标则默认生成第一个目标

Makefile的假想目标

通常情况下,我们可以用这个命令来清楚之前make的结构,中间文件和可执行文件等。

#上面还有其他内容被省略
clean:
	rm *.o test

但是当文件夹中有,名为clean的文件时,根据makefile的规则,clean文件已经存在并且它的依赖都比它旧(实际上是没有依赖)所有不会运行下面的代码,也就不会达到我们想要的效果,这种时候我们就需要用到假想目标这种语法了。

#上面还有其他内容被省略
clean:
	rm *.o test
.PHONY:clean

这样做旧依旧能够运行clean了。

Makefile的变量

Makefile的变量分为两种

  • :=:即时变量,该变量的值即刻确定,在定义时就被确定了
  • =:延时变量,该变量的值,在使用时才确定
  • ?=:延时变量,如果是第一次定义才起效,如果前面被定义过了就忽略这句
  • +=:附加,它是即时变量还是延时变量取决于 前面的定义
A:=$(C)
B=$(C)
C=abc#1

all:
	@echo A = $(A)
	@echo B = $(B)
C=123#2

会输出:

#1
A =
B = abc
#2
A =
B = 123
A:=$(C)
B=$(C)
C=abc

all:
	@echo A = $(A)
	@echo B = $(B)
C=123#1
C+=123#2

会输出:

#1
A =
B = 123
#2
A =
B = abc 123

Makefile函数

函数遍历

$(foreach val,list,text):对于list(通常用空格隔开)里的每一个变量执行text操作

A=a b c
B=$(foreach f,$(A),$(f).o)#用f代表A中的各个变量,执行第三个参数的操作。

all:
	@echo B=$(B)
#输出
B=a.o b.o c.o
filter函数过滤

$(filter pattern...,text):在text里面取出符合pattern格式的值

$(filter-out pattern...,text):在text里面取出不符合pattern格式的值

C= a b c d/
D=$(filter %/,$(C))
E=$(filter-out %/,$(C))

all:
	@echo D=$(D)
	@echo E=$(E)
#输出
D=d/
E=a b c
wildcard函数查找

$(wildcard pattern):pattern定义了文件名的格式,wildcard取出其中存在的文件

files = $(wildcard *.c)
files2=a.c b.c c.c d.c e.c
file3 = $(wildcard $(files2))
all:
	@echo files=$(files)
	@echo files3=$(files3)
#输出
files=a.c b.c c.c

也可以用这个函数确定哪些文件真实存在。

files2=a.c b.c c.c d.c e.c#其中 d.c e.c 并不存在于文件中
file3 = $(wildcard $(files2))
all:
	@echo files3=$(files3)
#输出
files3=a.c b.c c.c
patsubst函数替换

$(patsubst pattern,replacement,$(var)):从var中将符合patern格式的内容,替换为replacement。

files2=a.c b.c c.c d.c e.c abc
dep_files=$(patsubst %.c,%.d,$(file2))
all:
	@echo dep_files=$(dep_files)
#输出
files3=a.d b.d c.d d.d e.d abc

头文件依赖

在下述情况中,当我们修改了c.h中的内容,不加入新的一行,则c.h中的更改不会被编译,因为make认为对于c.o(%.o)这个目标,它的依赖c.c(%.c),并没有更改,所以我们就需要手动加入新的一行来解决这个问题,当要make时,我们运行到新加入的一行,发现c.h发生了修改,我们要生成新的目标c.o,如何生成呢,在下面的%.o:%.c下面告诉了我们如何生成。

test:a.o b.o c.o
	gcc -o test $^
c.o:c.c c.h#加入后可以解决

%.o:%.c
	gcc -c -o $@ $<
clean:
	rm *.o test
.PHONY:clean

但我们不能对每一个文件都这样操作,这样使用通配符将失去意义,我们的工作量也会回到最初的起点。这个时候,我们就可以用gcc的一个工具,它可以查看依赖,并生成文件。

gcc -M c.c #打印出c.c的依赖

gcc -M -MF c.d c.c  #把依赖文件写入文件c.d

gcc -c -o c.o c.c -MD -MF c.d #编译c.o,把依赖写入文件c.d

需要注意的是生成的.d文件是隐藏文件。也就是说它的前面还有一个点。.xx.o.d

具体做法如下

objs=a.o b.o c.o
dep_files := $(patsubst %,.%.d,$(objs)) #将objs中的所有文件替换成.%.d的形式 使用:= 是因为下一步中如果不这样会出现递归调用
dep_files := $(wildcard $(dep_files)) #将dep_files中确实存在的文件取出,赋给自己,这里用:= 即时变量是因为在这个语句中用到了它本身,但是如果使用=延时变量的话,会在用到dep_files时才生成dep_files,但是我用到的时候还没有,所有是错误的。

test: $(objs)
	gcc -o test $^
ifneq ($(dep_files),) #如果这个变量不等于空
include $(dep_files)
endif

%.o :%.c
	gcc -c -o $@ $< -MD -MF .$@.d

clean:
	rm *.o test
distclean:
	rm $(dep_files)

.PHONY:clean

CFLAGS

CFLAGS=-Werror :会把所有警告当成错误

CFLAGS=-Iinclude:将include目录添加到GCC搜索头文件默认的路径,

研究了一天了,本来想把 链接库文件也加进来,但是失败了。目前算是够用了,先这样吧。

.
├── bin
├── include
│   └── sum.h
├── lib
├── Makefile
└── src
    ├── sum.cpp
    └── main.cpp

以上是目录结构

VPATH = src:include:lib    #包含需要用到的文件所在的目录,不然makefile找不到
cc=g++
prom=sum
src=$(shell find ./ -name "*.cpp")	
include= $(shell find ./ -name "*.hpp") #使用shell命令,输出头文件相对当前目录的文件名,也就是待位置的文件名
obj= main.o sum.o					  #obj文件的宏定义。

CFLAGS = -g -Wall -I include            #-I include 是针对g++的,是头文件的位置,不加这个的话g++会报错,找不到头文件


$(prom):$(obj)	
	$(cc) -o $@  $^ 				# $@ 是所有的目标文件,写在这里的目的也就是让它输出为 $(prom) 即可执行文件的名字
    							   # $^ 是所有的依赖文件,也就是$(obj)

%.o:%.cpp							# %.o 就是要用到的 某.o 文件,后面就是 某.cpp 文件,注意 再一个目标中 % 所指代的
     $(cc) -c $^ $(CFLAGS)             #容是一样的,比如这个就分两次 代表了 main 和 sum 这个语句是会运行多次的。
	
.PHONY : clean	                      # .PHONY 即伪目标。
clean:
	rm -rf $(prom) $(obj)
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肥杨同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值