Makefile 与 GCC G++ 入门

2023-11-12

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)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Makefile 与 GCC G++ 入门 的相关文章

  • 注销租约抛出 InvalidOperationException

    我有一个使用插件的应用程序 我在另一个应用程序域中加载插件 我使用 RemoteHandle 类http www pocketsilicon com post Things That Make My Life Hell Part 1 App
  • 提交后禁用按钮

    当用户提交付款表单并且发布表单的代码导致 Firefox 中出现重复发布时 我试图禁用按钮 去掉代码就不会出现这个问题 在firefox以外的任何浏览器中也不会出现这个问题 知道如何防止双重帖子吗 System Text StringBui
  • 在 C 中匹配二进制模式

    我目前正在开发一个 C 程序 需要解析一些定制的数据结构 幸运的是我知道它们是如何构造的 但是我不确定如何在 C 中实现我的解析器 每个结构的长度都是 32 位 并且每个结构都可以通过其二进制签名来识别 举个例子 有两个我感兴趣的特定结构
  • 复制目录内容

    我想将目录 tmp1 的内容复制到另一个目录 tmp2 tmp1 可能包含文件和其他目录 我想使用C C 复制tmp1的内容 包括模式 如果 tmp1 包含目录树 我想递归复制它们 最简单的解决方案是什么 我找到了一个解决方案来打开目录并读
  • 如何区分用户点击链接和页面自动重定向?

    拥有 C WebBrowser control http msdn microsoft com en us library system windows forms webbrowser aspx在我的 WinForms 应用程序中 并意识
  • 获取两个工作日之间的天数差异

    这听起来很简单 但我不明白其中的意义 那么获取两次之间的天数的最简单方法是什么DayOfWeeks当第一个是起点时 如果下一个工作日较早 则应考虑在下周 The DayOfWeek 枚举 http 20 20 5B1 5D 3a 20htt
  • 将 Word 文档另存为图像

    我正在使用下面的代码将 Word 文档转换为图像文件 但是图片显得太大 内容不适合 有没有办法渲染图片或将图片保存到合适的尺寸 private void btnConvert Click object sender EventArgs e
  • 在 Visual Studio 2010 中从 Fortran 调用 C++ 函数

    我想从 Fortran 调用 C 函数 为此 我在 Visual Studio 2010 中创建了一个 FORTRAN 项目 之后 我将一个 Cpp 项目添加到该 FORTRAN 项目中 当我要构建程序时出现以下错误 Error 1 unr
  • 在一个平台上,对于所有数据类型,所有数据指针的大小是否相同? [复制]

    这个问题在这里已经有答案了 Are char int long 甚至long long 大小相同 在给定平台上 不能保证它们的大小相同 尽管在我有使用经验的平台上它们通常是相同的 C 2011 在线草稿 http www open std
  • DbContext 和 ObjectContext 有什么区别

    From MSDN 表示工作单元和存储库模式的组合 使您能够查询数据库并将更改分组在一起 然后将这些更改作为一个单元写回存储 DbContext在概念上类似于ObjectContext 我虽然DbContext只处理与数据库的连接以及针对数
  • x86-64 AMD 上 CALL 指令的操作数生成

    以下是示例程序 objdump 的输出 080483b4
  • 如何在 Xaml 文本中添加电子邮件链接?

    我在 Windows Phone 8 应用程序中有一些大文本 我希望其中有电子邮件链接 例如 mailto 功能 这是代码的一部分
  • CMake 无法确定目标的链接器语言

    首先 我查看了this https stackoverflow com questions 11801186 cmake unable to determine linker language with c发帖并找不到解决我的问题的方法 我
  • AES 128 CBC 蒙特卡罗测试

    我正在 AES 128 CBC 上执行 MCT 如中所述http csrc nist gov groups STM cavp documents aes AESAVS pdf http csrc nist gov groups STM ca
  • 为什么 gcc 抱怨“错误:模板参数 '0' 的类型 'intT' 取决于模板参数”?

    我的编译器是gcc 4 9 0 以下代码无法编译 template
  • 使用 %d 打印 unsigned long long

    为什么我打印以下内容时得到 1 unsigned long long int largestIntegerInC 18446744073709551615LL printf largestIntegerInC d n largestInte
  • C++ 函数重载类似转换

    我收到一个错误 指出两个重载具有相似的转换 我尝试了太多的事情 但没有任何帮助 这是那段代码 CString GetInput int numberOfInput BOOL clearBuffer FALSE UINT timeout IN
  • 调用堆栈中的“外部代码”是什么意思?

    我在 Visual Studio 中调用一个方法 并尝试通过检查调用堆栈来调试它 其中一些行标记为 外部代码 这到底是什么意思 方法来自 dll已被处决 外部代码 意味着该dll没有可用的调试信息 你能做的就是在Call Stack窗口中单
  • 如何部署“SQL Server Express + EF”应用程序

    这是我第一次部署使用 SQL Server Express 数据库的应用程序 我首先使用实体 框架模型来联系数据库 我使用 Install Shield 创建了一个安装向导来安装应用程序 这些是我在目标计算机中安装应用程序所执行的步骤 安装
  • C++ 条件编译

    我有以下代码片段 ifdef DO LOG define log p record p else define log p endif void record char data 现在如果我打电话log hello world 在我的代码中

随机推荐