Contents
GCC
header 头文件
include 使用 “” or <> : precedence is different (mine? Or system’s.)
gcc 在编译时如何去寻找所需要的头文件 :
- 使用双引号包含的头文件会首先搜索当前工作目录
- 然后与<XXX> 一致, header file的搜寻会从 -I 开始
- 然后找gcc的环境变量 C_INCLUDE_PATH,CPLUS_INCLUDE_PATH,OBJC_INCLUDE_PATH
- 再找内定目录
/usr/include
/usr/local/include(centos7中该目录下是空的)
gcc的一系列自带目录
- e.g. /usr/include/c++/x.x.x
- 如果想引用位于标准位置之外的头文件,需要在调用编译器的时候加上-I标志,来显式的说明头文件所在文件夹,e.g: $ gcc -I/usr/openwin/include hello.c
lib 库文件
编译的时候gcc
- 搜索-L指定目录;
- 再找gcc的环境变量LIBRARY_PATH
- 再找内定目录 /lib:/usr/local/lib :/usr/lib
- 这是当初compile gcc时写在程序内的
- /usr/local/lib prior to /usr/lib
运行时动态库的搜索路径 :
- 编译目标代码时指定的动态库搜索路径;
- 环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
- 配置文件/etc/ld.so.conf中指定的动态库搜索路径;
- 默认的动态库搜索路径/lib, /usr/lib
Makefile
Recommended
弄懂以上两份材料, makefile的书写也就入门了,剩下的就得在实际工作中磨砺。
如果你没有时间阅读或者第一次学习感觉吃力,以下是个人总结和补充。
Autotools
-
Windows下的IDE都自动生成了Makefile. Linux下自动生成Makefile的工具有autotools、qmake 、Cmake 等.
-
@@在做一个大的项目的时候,不可能都靠人来写Makefile,这样太麻烦了!很多GNULinux的的软件都是用它生成Makefile的,包括我们非常熟悉的Linux内核源代码。
-
对于开源或者大型项目,一般都使用工具自动生成Makefile。比如GNU开源软件,一般是使用Autotools,还有Cmake、Scons等。
-
对于小项目,使用Makefile还是很方便的的,至少手写的Makefile可读性很好,维护成本也不大。
-
学习Makefile还是很有必要的,至少会让你对软件构件系统、程序的编译和链接、库的生成和使用有一个直观的理解。特别是对从事底层开发、调试大有裨益。
对于从事应用、业务逻辑软件开发,建议还是使用自动工具,时间精力允许,多学也是总有益处,艺不压身。 -
autotools给人感觉好像是到处都在使用,几乎可以代替make。但事实上非常多的大项目的makefile还是手写的(So,具体怎么选择还是取决于你的项目、能力以及及老板口味)。
-
autotools核心是解决移植性的问题,学习成本其实是很高的;对于初学者,用make来完成你自己小型项目的编译,学习成本要小的多;即使是公司中,很多内部使用的项也目都是使用手写makefile来管理。@
-
听起来很乱,其实是因为这个问题本身就没有标准答案。我能告诉你的就是:如果你没得选,就不要瞎纠结;有的选,就多学点吧,毕竟业务层的技术变化这么快,基本功不行的话,终究要还的, 这也是科班与半路出家的差距,不要太狂,要正视不足。
具体使用时,Linux下,小工程可手动写Makefile; 大工程用automake来帮你生成Makefile;要想跨平台,就用cmake。如果GUI用了Qt,也可以用qmake+*.pro来管理工程,这也是跨平台的。当然,cmake中也有针对Qt的一些规则,并代替qmake帮你将qt相关的命令整理好了
how to write your makefile
首先你必须牢牢记住:
Makefile concerns the compiling rules for the whole project. 它关心的是文件之间的依赖关系。
“target” 依赖”prerequisites”, 其生成规则定义在command. If “Prerequisites” 中 any file(s) is(are) newer than “target”, “command” 所定义的命令就会执行. This is Makefile’s rule , its core.
- 一个makefile文件包含五个要素:显式规则、隐晦规则、变量定义、文件指示、注释。
- 显式规则需要自己写;
- 隐晦规则是自动被支持的,这“隐晦”二字也是学习makefile的难点,如果你没能深入理解这类规则,很难看懂makefile里都是些甚。
- makefile中的变量,准确地讲更像是宏macro(但是可以修改),是个字符串string,会被自动替换,自然也就不存在变量类型一说。
- 可以使用#include<filename> / #if 如果你不是只学过python,应该不陌生吧。
- 注释采用 “#” 单行注释,类似“//”。 其实这也是比较通用的注释方式,shell、python等脚本语言都采用此方式。
- 一个makefile 的基本框架
Target ……… : prerequisites ………
Command ………
.PHONY : clean
Clean :
-rm exe_name $(objects)
- 目标文件 ObjectFile, or 执行文件, or label. 第一行的第一个目标is default.
- prerequisites 是生成target 所需要的文件or目标.
- Make 所需要执行的命令, any shell command(executed by standard hell/bin/shell)
- .phony 伪目标.
- “clean” is like a “label” in C. 当然,C语言也不提倡使用label。
- Difference between “rm” and “-rm” : maybe some files error, do not care, continue!
- make 的执行步骤
- Save as ‘Makefile” or “makefile ”;
- 在该目录下直接输入命令”make” ,you can get the executable file.
- “make” only concern “dependency”, if error then aborted with ERRNO, it doesn’t care about the “commands”(even wrong)
- use command “make clean” to execute “%directives%” specified in clean:
The commands succeeding “clean” cannot execute automatically for there is no “dependency ” after colon. 也就是说你要用make clean之类的命令。
读取makefile的顺序 In order :GNUmakefile() > makefile(some platform may support this only) > Makefile(recommended).
You can also use other user defined name and use “make -f filename”
- 所谓“更新newer” Including the case that “target” doesn’t exist,this process is similar to stack—nested–call.
- Each commands line begin with a “tab”.
If succeeding commands continues after the preceding: write them in the same line, separated by semi-colon.
Use “make -I “ or “-command” to ignore the errors,instead of aborting.
变量
- definition and assignment
- use by ${var} or $(var)
var = value Vs var := value
“=” permits 用尚未初始化”的var to assign, which may cause 死循环.
“:=” only permit those initialized in advance.
“?=” if already defined , do nothing.
“+=” append.if undefined before”= ”; else inheritance
- 变量替换
- $(var:a=b) or ${var:a=b} : replace substring “a” in string var by b.
- 静态模式 $(var:%.o=%.c)
-
多行变量
Define …… endef -
环境变量
CFLAGS 统一的编译参数.
在makefile中可以使用上一层级定义的变量 : 在命令行定义的变量能直接传递给下层; if defined in file, use “export” to declare. -
目标变量
<target… > : -
模式变量
<pattern …> : [override]
如果你记不住, 用到再来查看吧.
隐含规则
“make” can derive files & commands automatically.
- [Whatever.c/.h files] can be derived automatically , into [whatever .o file].
- %头文件依赖自动生成%@如果头文件路径在cpp路径或者在搜索路径下,不用在Makefile下包含; 头文件如果头文件路径搜索不到,那么在g++里加上 -I …/…/ 选项。如g++ -I /usr/include -o test test.cpp%
用隐晦规则时 .o 文件会自动找对应的 .c文件.然后编译(%自动搞定header%)but if you changed header files—>wrong!!(source files not changed, makefile 以为啥都没变……) 所以得想办法。既能使用隐晦规则.又能包含了头文件.这就是最高宗旨.
- gcc编译器有个功能 gcc -MM first.c 这样会输出first.o : first.c *.h
if 在编译时加上-M选项, print the library files too, in GNU, it should be -MM.
-MMD选项 会把自动得到的依赖信息output into *.d file.
GNU organization recommends compilers to generate a”name.d” file for each source file, containing the dependency automatically got .
以下代码相信你迟早会碰到:
这段代码的核心意思就是"把每个源文件都用gcc -MM输出从定向到一个对应的.d文件. "
- @ —放在最前表示"不显示执行的命令", 通俗来讲就是"悄悄地进村,打枪地不要",这不光是shell中,在windows 批处理脚本也这么干.
- set -e — 1. 当命令的返回值为非零状态时,则立即退出脚本的执行(防止一错再错)。 2. 作用范围只限于脚本执行的当前进行,不作用于其创建的子进程。
- 用分号分隔命令,续行符续行.
- rm是删除命令,"-f"指令要慎用!! “$@“表示target,这里就是指”%.d”,将依赖关系输出到.d 文件前需要将旧的删掉, 这也是编码的基本套路—在写文件之前先判断是否已经存在.
- 第三行是重点. CC/CPPFLAGS是一个变量,这是优秀程序员的基本素质,因为编译器不止gcc一种, 所以用CC指代. 类似的思想存在于c/c++的macro/typedef , 当然, 使用const是推荐做法. $< 指代第一个依赖文件,即%.c; ">"是重定向输出符,使用linux环境的没有理由看不懂~. $@.$$ 中 中 中$$代表随机编号, 得到类似shuaige.d.123455的文件.
- sed的官方定义是"stream editor for filtering and transforming text".该sed命令就实现了把main.o :替换为main.o main.d :的目的。
关于sed, 这里多说几句2 usage types : sed [options] ‘command’ file(s) & sed [options] -f scriptfile file(s)
s命令$ sed ‘s/test/mytest/g’ example-----在整行范围内把test替换为mytest。如果没有g标记,则只有每行第一个匹配的test被替换成mytest。
Here, use comma as delimiter.
($*).o[ :]替换为\1.o $@ :
$,表示的是target的除去了suffix后的filename,也就是%.d: %.c当中的%部分。
伪目标
目标和伪目标也可以作为依赖条件.
伪目标的特点是"总是被执行", 也就是不用检查是否"newer".
条件表达式
makefile 其实很复杂,本身就是一门语言.
: ifeq / ifneq /ifdef / ifndef
<conditional-directive>
<text-if-true>
Else
<text-if-false>
Endif
函数
既然makefile是一门程序设计语言,自然少不了函数了.
**$(functionname arguments) **
Seperated by whitespace; arguments separated by comma.
A few functions in make, but enough .
你只能使用系统自带的函数,不能自定义.
下面介绍几个常用函数.
- 字符串替换
$(Subst <from>, <to>,<text>)
Repalce <from> in string <text> with <to>.
run makefile
- 【make中命令行前面加上减号】
忽略当前此行命令执行时候所遇到的错误。
而如果不忽略,make在执行命令的时候,如果遇到error,会退出执行的,加上减号的目的,是即便此行命令执行中出错,比如删除一个不存在的文件等,那么也不要管,继续执行make。
【make中命令行前面加上@】
就是,在make执行时候,输出的信息中,不要显示此行命令。
而正常情况下,make执行过程中,都是会显示其所执行的任何的命令的。如果你不想要显示某行的命令,那么就在其前面加上@符号即可.
一种推荐做法是用一个"宏", 比如加个变量echo=@, 然后在各行前面加上$(echo),如果需要显示,就把echo值设为空 . 就像c编程中 #define_DEBUG 或者#define STATIC static, 好的思想总是相通的嘛! 学习得要能够举一反三, 这也是为什么高手的知识面一般都很广.
debug
makefile还可以类似debug, 不过没有其它语言那么复杂.
makefile debug
–debug[=<options>]
-n : print the commands that would be executed , but do not execute them.
记不住也很正常, 如果连这些你都能记住,要么你明早有个面试,要么你明早要去招人.
嵌套 nested
在一些大的工程中,我们把不同模块或不同功能的源文件放在不同的目录, we can write a Makefile in every dir; then write a “总控Makefile”. 这种分级设计模式,太常见了,不论软件还是硬件都有它忙碌的身影. 机械/汽车等行业也把"模块化/层次化挂在嘴上".
$(MAKE) 宏变量 传递到下级.
-w argument: print current direcory when executing make.
make -p 可以查看所有预定义的变量的当前值。
System var”MAKELEVEL” : if make has a nested-executed action, MAKELEVEL stores the present 调用层数.
Make target
All: 这个伪目标是所有目标的目标, generally compile all targets.
Clean: remove all files generated by make.
Install: 安装已经编译好的程序,in fact, copy the target executable file into 指定目标。 在linux命令行界面下安装过软件的兄弟应该见过它.
Print : 列出改变过的源文件;
Tar : 打包
TAGS: update all targets , for 完整地重编译使用
Check& test : 测试makefile.这里的check/test是指你自己定义的伪目标,不是debug指令.
杂记
一些常用的变量系统给出了预定义值
AR: default is “ar”;
AS: default is “as”;
CC: default is “cc”;
CXX: default is “g++”;
CPP: c 程序的预处理器(output: stdout) , default is “$(CC) -E”
RM: 删除文件命令 , default is “rm -f”.
部分常用的自动化变量
$@ : target file set.
$(@D) : 目录部分;
$(@F) : file part;
$% : 目标中的函数库文件
$< : 依赖目标中第一个目标名
$? : 所有比目标新的依赖目标的集合, seperated by whitespace.
$^ : all 依赖目标的集合, 去重;
$+ : all 依赖目标的集合, 不去重;
$* :$* 这个变量表示目标模式中“%”及其之前的部分。如果目标是“dir/a.foo.b”,并且目标的模式是“a.%.b”,那么,“$*”的值就是“dir/a.foo”。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么“$*”也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么“$”就是除了后缀的那一部分。例如:如果目标是“foo.c”,因为“.c”是make所能识别的后缀名,所以,“$*”的值就是“foo”。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用“$*”,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么“$”就是空值。
更多有关make的细节,请参考man make.