Ubuntu Linux编程快速入门(0-1)-Ubuntu的基本使用-GNU编译器套装和Makefile

gcc和g++的基本使用

  • gcc主要用来编译C代码,g++主要用来编译C++代码。合法的C++代码不一定能用gcc编译(往往会在连接时报错)。笔者个人没有遇到过g++编译C代码出错的情况,但是一定要用C语言时,还是使用gcc稳妥些。
  • 本文以g++为例讲述用法,gcc大体类同。但这并不意味着gcc和g++可以随意混用。
  • gcc和g++中很多选项是无所谓顺序的,这里笔者是按自己的习惯写的。实际上,在多数情况下,调换成其他顺序不会有影响。

基本选项

g++ <输入文件>

从指定文件生成可执行文件。通常,默认输出的文件名为a.out。

g++ <输入文件> -o <输出文件>

-o选项用于指定输出文件名。
一般而言,为了缩减编译时间和代码解耦,我们会把代码拆分成供用户使用的头文件(.h,.hpp等)和实现文件(.cpp,.cc等),这样,修改了某个源文件的话,只需要编译被修改的源文件并重新链接所有相关的目标文件即可。当然,含模板的头文件比较特殊,不过这里我们只讨论一般的模块封装,不考虑模板之类需求。这并不是说有模板就一定要特殊的解耦技巧,这肯定是要具体情况具体分析的。

g++ -c <输入文件> -o <输出文件>

-c选项用于将源代码编译成目标文件。
习惯上,我们令输出文件的后缀名为.o,但并不是一定要这样。

g++ <目标文件列表> <其他选项> -o <可执行文件名>

链接时不需要额外的选项。链接是最后一步,而且g++能够通过输入文件自动地得知从哪一步开始执行到链接,因此只要列出所有目标文件和库即可。

g++ <其他选项> -std=<语法标准>

-std选项用于指定编译所使用的语法标准,例如希望使用C++11特性,那么加入选项-std=c++11即可。

g++ <其他选项> <链接库>

链接过程中,常常需要一些你的代码里没有的外部符号,这时就需要链接库选项来指定符号所涉及库的位置。多数情形下,库的名称都是lib<库名>.so<.版本>构成的,那么链接选项就用-l<库名>就行了。例如,要链接libopencv_core.so.3.1.0,就用-lopencv_core选项。但也有例外,例如,链接pthread库时用的是-pthread,也有老版本系统要用-lpthread的,但是现在似乎不太常见后者了,除非是要考虑兼容性的场景。

g++ <其他选项> -I<头文件目录>

如果没有在环境变量指定的路径中找到include预编译指令指定的文件,预编译器会在-I选项指定的头文件目录下寻找。

g++ <其他选项> -L<库目录>

如果没有在环境变量指定的路径中找到需要的符号,链接器会尝试在-L选项指定的库目录下寻找。

其他常用选项

我们知道,从C/C++源代码到可执行文件,主要要经过预编译、编译、汇编、链接四个过程,-c选项即是只运行到汇编之后。

g++ -E <其他选项>

使用-E选项来获得预编译文件。
笔者习惯将预编译获得文件的扩展名定为.i。

g++ -S <其他选项>

使用-S来获得汇编代码(即只进行到完成编译)。
习惯上,汇编代码文件一般使用.s或.asm作为扩展名。

g++ <其他选项> -static-libgcc

-static-libgcc用于指定静态链接gcc标准库,也有其他类似的-static-lib选项。

g++ <其他选项> -O<n> 

n是0,1,2,3,或是s。该选项指定优化等级,从-O0到-O3依次提高;而-Os则是笔者不甚了解的。此外,还有一些以-f开头的选项用于指定其他优化。当然,并不是所有-f开头的选项都和优化相关。

g++ -Wall <其他选项>

该选项用于显示任何警告信息。一般而言,我们希望代码尽可能不报任何东西,包括任何错误和警告。那样的话,才能基本保证我们的代码在语法上绝对安全。多数情形下,如果有人告诉你他/她的代码报了警告不用管的话,要么就是实力极强手撕编译器的人物,……要么就是纯粹的萌新。当然,也可能是正好碰到了编译器的bug之类的,虽然概率不高。

g++ <其他选项> -D<宏名>

gcc和g++允许在调用预编译器时加入新的宏。比较典型的是-DDEBUG选项,用于生成调试版程序。
此外还有其他调试选项等,由于不是那么常用,在此不再赘述。
此外,若要编译自己的.a文件(即静态库),要用到ar指令。

ar rv lib<库名>.a <目标文件列表>

这会把目标文件列表中的文件都打包成一个.a文件,使用时在链接选项中加入-l<库名>即可,这也是为什么习惯上将文件名前三个字母定为lib。写成其他形式的话,与链接指令的目标文件列在一起就行了。如果失败,可以试试把rv选项改为crv。

Makefile基础

笔者的Makefile风格一向简单粗暴,能用就行。若是读者想要掌握其精髓,可以考虑陈皓前辈的博文。本文则只介绍最基本的内容。

基本规则

执行方法

要运行一个Makefile文件,使用指令:

make <其他选项>

没有其他选项时,会按照默认的规则,执行当前目录下的Makefile文件内容(若存在)。
Makefile使用#字符作为注释符,和shell类似。
Makefile的语法规则中,遇到的第一个变量被作为最终目标。如果最终目标依赖的文件或其他目标没有更新,那么最终目标也不会被更新。

变量和目标

定义变量遵从以下语法:

<变量名>:=<字符串>

直接改变其值可以用

<变量名>=<字符串>

注意,通常,除非该变量在环境变量中被定义或由make指令传入,否则没有经过:=定义就直接=赋值是会失败的。
在已定义的变量后加上字符串,用

<变量名>+=<字符串>

特别地,.PHONY定义的是伪目标,不过一般而言和普通目标一样看待就可以。一般而言,把.PHONY放在首行也不会被解析为最终目标。变量在定义、赋值时,所在行行首不要有多余的字符。
通常,.PHONY用来定义清除已生成目标的指令。
若目标是一个或一组指令:

<目标名>:<依赖目标名>
	<指令1>
	<指令2>
	<...>

每个指令所在行的行首必须有且仅有一个Tab字符。依赖指的是在目标下的指令被执行前,用户指定的必须先完成的目标。常见的情形是,由源文件编译成目标文件(没有依赖),再由目标文件链接成可执行文件(依赖是编译好的目标文件)。

在前述两种行首规定之外,通常可以在运算符两侧和作为变量值的字符串中间加入空格和Tab字符,不过Makefile调用函数中一般有着固定格式,这时不能多加内容,也不能少加。本文作为Makefile基础教程,不介绍这些Makefile的函数。
那么,一个基本的Makefile差不多是这样的:

TARGET	:= main
main	: main.o
	g++ main.o -o main
main.o	:
	g++ -c main.cpp -o main.o

实际上,我们一般不会直接写死指令相关的目标名,而是通过变量来指定,这样方便修改。

$(<变量名>)

类似于PHP等语言,$字符用于调取变量的值,而且允许嵌套,类似这样:

TARGET		:= show
a			:=	10
b			:=	a
$(TARGET)	:
	echo $($(b))

执行结果:

echo 10
10

$(b)是b的值a,而$(a)则是10。
这里我们注意到,要执行的指令本身也被输出了。这是make的默认选项,可以使用-s关闭。
如果读者尝试去掉括号,会发现变量未被正确解析,但少了一个$字符。因此,尽管不是语法强制要求,但是使用变量的值时请加上括号
在Makefile语法中也定义了CC等指定编译器的变量,但笔者个人习惯手动指定g++编译套装。
习惯上常用的变量名主要有:

TARGET		:=<最终目标名>
OBJS		:=<需要被编译得到的目标文件列表>
LIBS		:=<链接库选项列表>
CC			:=<C编译套装>
CXX			:=<C++编译套装>
LD			:=<链接器>
CPPFLAG		:=<C预编译选项>
CXXFLAG		:=<C++预编译选项>
LDFLAG		:=<链接选项>

也有些用户会使用复数形式-FLAGS。
.PHONY声明伪目标,一般列出不是文件的目标即可。这么理解有些简单粗暴,不过这么做基本上是正确的。最常见的伪目标是clean,用于清除已经生成的中间文件。在clean中则使用Makefile语法中预定义的RM变量可以删除文件,它的值是rm -f。
于是,一个典型而且简单的、实用的Makefile可以是这个形式:

TARGET		:= main
CXX			:= g++
LD			:= g++
CXXFLAG		:= -O3 -std=c++17
LDFLAG		:= -static-libgcc

$(TARGET)	: $(TARGET).o
	$(LD) $(TARGET).o -o $(TARGET) $(LDFLAG)
$(TARGET).o	:
	$(CXX) -c $(TARGET).cpp -o $(TARGET).o $(CXXFLAG)
.PHONY		: clean
clean		:
	$(RM) $(TARGET).o $(TARGET)

进阶规则

执行选项

make -n

使用-n选项,可以知道make会执行哪些shell指令,但不实际执行。调试Makefile时很有用。

make -j<n>

-j选项指定make最多同时执行几个目标。其中,n是一个整数。例如-j4就是同时执行4个目标。缺省状态下,make通常会尝试同时执行尽可能多的目标。

make <变量名>=<>

这个选项可以指定变量的值。尽管对于Makefile文件内未定义的变量也可 如此赋值,笔者还是习惯在Makefile内使用:=定义为空值。

TARGET		:= show
a			:=	10
$(TARGET)	:
	echo $($(b))

运行指令和结果:

> make
echo 

> make b=a
echo 10
10

这里也可看出来,未定义的值当作空值处理

特殊目标、变量和符号

在任何目标中,$^表示当前目标的全部依赖,而$@表示当前目标名。

TARGET		:= a
a			: b c
	echo $^
	echo $@
b			:
c			:

执行make的结果:

echo b c
b c
echo a
a

%字符是用于字符串模式匹配的通配符。一般用于自动编译目标文件。

TARGET		:= all
all			: co bo ao cd bd ad
	
%o			:
	echo $@ 1
%d			:
	echo $@ 2

运行make指令的结果:

echo co 1
co 1
echo bo 1
bo 1
echo ao 1
ao 1
echo cd 2
cd 2
echo bd 2
bd 2
echo ad 2
ad 2

从这里也可看出来,Makefile中依赖的执行顺序与定义顺序有关
要调用shell命令,可以使用

$(shell <命令>)

它的值是该指令的输出内容。

TARGET		:= all
CONTENT		:= -pthread $(shell pkg-config --libs opencv)
all			:
	echo $(CONTENT)

执行make的结果(当然,只有安装了opencv才看得到这个结果):

echo -pthread -L/usr/local/lib -lopencv_dnn -lopencv_ml -lopencv_objdetect -lopencv_shape -lopencv_stitching -lopencv_superres -lopencv_videostab -lopencv_calib3d -lopencv_features2d -lopencv_highgui -lopencv_videoio -lopencv_imgcodecs -lopencv_video -lopencv_photo -lopencv_imgproc -lopencv_flann -lopencv_core
-pthread -L/usr/local/lib -lopencv_dnn -lopencv_ml -lopencv_objdetect -lopencv_shape -lopencv_stitching -lopencv_superres -lopencv_videostab -lopencv_calib3d -lopencv_features2d -lopencv_highgui -lopencv_videoio -lopencv_imgcodecs -lopencv_video -lopencv_photo -lopencv_imgproc -lopencv_flann -lopencv_core
$(error <报错内容>)

error用于强制报错终止make指令。

条件判断

ifeq (<值a>,<值b>)
	<指令>
<else>
	<指令>
endif
ifdef <变量名>
	<指令>
<else>
	<指令>
endif

使用ifeq和ifdef可进行简单的条件判断。

TARGET		:= all
all			:
	
ifdef arg
	echo arg defined
ifeq ($(arg),test)
	echo arg == 'test'
else
	echo arg != 'test'
endif
else
	echo arg not defined
endif

执行命令和结果:

> make -s
arg not defined
> make -s arg=
arg not defined
> make -s arg=qwerty
arg defined
arg != test
> make -s arg=test
arg defined
arg == test

这里,除了条件判断语句的语法外,还有几点值得注意:
空值和未定义常常有相似的效果
Makefile中,字符串常值不需要加上任何额外符号。相信各位读者已经看出这点了。
Makefile的条件判断可以嵌套,但注意缩进。出于这个原因,不建议使用过于复杂的嵌套。
此外,若希望用户执行时必须指定选项,可以借助伪目标完成。

一个简单的Makefile示例

.PHONY				: compile run clean no-option
no-option			:
	$(error Option required)

TARGET				:= <工程名>
OBJS				:= <希望得到的目标文件的列表> $(TARGET).o
LIBS				:= <库列表> $(shell pkg-config --libs <库名>)
MACRO				:=

CXX					:= g++
LD					:= g++
CXXFLAG				:= -O3 -std=c++17 $(MACRO)
LDFLAG				:= -static-libgcc $(LIBS)

$(TARGET)			: $(OBJS)
	$(LD) $^ -o $@ $(LDFLAG)
%.o					: %.cpp
	$(CXX) -c $^ -o $@ $(CXXFLAG)

compile				: $(TARGET)
run					:
	./$(TARGET)
clean				:
	$(RM) $(TARGET) $(OBJS)

这里,在clean中加入OBJS也很大程度上是一种习惯。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值