网上很多人写的都是一些原理的文章,很少讲一些变量的含义,这里整理一下,根据目录跳转快速查阅。
专业但是不好理解博文:https://blog.csdn.net/haoel/article/details/2886
图形解释比较好理解的博文:https://zhuanlan.zhihu.com/p/47390641
会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。因为,makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“#”。
默认情况下,GNU工具链编译过程中,控制台输出的一复行信息是不换行的,
这样,制当输出信息过长时(如编译错误时的信息百),会导致你无法看到完整的输出信息,
加入-fmessage-length=0后,输出信息会根据控制台的宽度自动换行,这样就能看全输度出信息了。
-w 禁止编译警告的打印。这个警告不建议使用。
一些代码笔误导致的BUG,这些问题可以从编译警告中知道。
-Werror 将所有的警告当成错误处理。此选项谨慎建议加上。
有的开源库警告很多(大名鼎鼎的ffmpeg也有很多警告呢),一一改掉耗时耗人力,必要性也不大。
-Wfatal-errors 遇到第一个错误就停止,减少查找错误时间。建议加上。
很多人遇到错误,没有意识到从第一个开始排查。不管是编译错误,还是程序运行出错,从最开始的错误查起,是个好的做法。
-Wall 开启“所有”的警告。强烈建议加上,并推荐该选项成为共识。
如case语句没有default处理,有符号、无符号处理,未使用变量(特别是函数有大量未使用的数组,占用栈空间,测试发现,开辟一个未使用的8MB的数组,程序有coredump),用%d来打印地址,或%s打印int值,等,都可以发出警告。
-Wextra 除-Wall外其它的警告。建议加上。
在GCC编译时,加上必要的警告选项,可以避免很多低级错误引发的问题,实际工程代码中遇到用“==”来赋值,出现过把“=”当成判断的。但是,有些错误却不是用GCC选项能解决的。比如一般项目都会自定义调试信息打印函数,但在处理可变参数类型时,往往不注意。可参考文章《一个可变参数类型检查的示例》。
-Wno-unused-parameter
项目在开发的时候可能要求将所有的warning 都暴露出来,在gcc 编译的时候需要加上flag -Wall。如果没有使用的变量或者函数,就会在这个时候暴露出来,并提示如下信息:error: unused parameter ‘spid’ [-Werror=unused-parameter]
$ gcc -Q --help=optimizers -O3
...
-fassociative-math [disabled]
-fassume-phsa [enabled]
...
POSIX线程(英语:POSIX Threads,常被缩写为Pthreads)是POSIX的线程标准,定义了创建和操纵线程的一套API。
Linux没有真正意义上的线程,它的实现是由进程来模拟,所以属于用户级线程,位于libpthread共享库(所以线程的ID只在库中有效),遵循POSIX标准。
Open Multi-Processing的缩写,是一个应用程序接口(API),可用于显式指导多线程、共享内存的并行性。
在项目程序已经完成好的情况下不需要大幅度的修改源代码,只需要加上专用的pragma来指明自己的意图,由此编译器可以自动将程序进行并行化,并在必要之处加入同步互斥以及通信。当选择忽略这些pragma,或者编译器不支持OpenMp时,程序又可退化为通常的程序(一般为串行),代码仍然可以正常运作,只是不能利用多线程来加速程序执行。OpenMP提供的这种对于并行描述的高层抽象降低了并行编程的难度和复杂度,这样程序员可以把更多的精力投入到并行算法本身,而非其具体实现细节。对基于数据分集的多线程程序设计,OpenMP是一个很好的选择。
OpenMP支持的语言包括C/C++、Fortran;而支持OpenMP的编译器VS、gcc、clang等都行。可移植性也很好:Unix/Linux和Windows
连接的时候不使用标准系统的启动文件。标准系统库通常被使用,除非选项“-nostdlib”和“-nodefaultlibs”被使用。
连接的时候不使用标准系统库。只有你指定的库才能够传递给连接器。与系统库有关的特定的连接选项(例如-static-libgcc 和 -shared-libgcc )将会被忽略。标准的启动文件通常会被使用,除非-nostartfiles选项被使用。
编译器可能会生成memcmp,memset,memcpy和memmove的调用,这些entries通常会被标准库libc中的相关的entries解决。当这个选项被使用的时候,这些入口点应该通过其他的方法被提供。
链接的时候不使用标准的系统启动文件和系统库。 没有启动文件和只用你指定的库可以被传递给连接器。与系统库有关的特定的连接选项(例如-static-libgcc 和 -shared-libgcc )将会被忽略。编译器可能会生成memcmp,memset,memcpy和memmove的调用,这些entries通常会被标准库libc中的相关的entries解决。当这个选项被使用的时候,这些入口点应该通过其他的方法被提供。
如果目标支持任意数目的节(sections),把每个函数和数据放置在输出文件各自的节中(sections)。 这个函数和数据的名字决定了在输出文件里各自所在节(section)的名字。
在某个系统上使用这个选项,这个系统上的连接器能够执行优化,改善指令空间里面的本地索引。使用ELF作为目标文件格式的系统和运行在SPARC处理器上的Solaris2系统的连接器有这个选项。AIX系统的连接器将来可能有这个选项。
当这样做有重要意义的时候才使用这个选项。当你使用这个选项的时候,汇编器和连接器会生成很大的目标文件和执行文件,整个过程会很慢。如果你在你的系统上使用了这个选项,就不行在你的系统上使用gprof这个程序。 当这个选项和-g一起使用时,你再调试的时候可能会遇到问题。
条件语句中使用到了三个关键字:“ifeq”、“else”和“endif”。其中:
“ifeq”表示条件语句的开始,并指定了一个比较条件(相等)。
之后是用圆括号括包围的、使用逗号“,”分割的两个参数,和关键字“ifeq”用空格分开。
参数中的变量引用在进行变量值比较时被展开。
“ifeq”之后就是当条件满足make需要执行的,条件不满足时忽略。
“else”之后就是当条件不满足时的执行部分。不是所有的条件语句都需要此部分。
“endif”表示一个条件语句的结束,任何一个条件表达式都必须以“endif”结束。
ifneq是比较两个参数是否相同。
ifneq ( ( B O A R D H A V E B L U E T O O T H B C M ) , ) 第 二 个 参 数 空 就 是 N U L L 意 思 是 (BOARD_HAVE_BLUETOOTH_BCM),) 第二个参数空就是NULL 意思是 (BOARDHAVEBLUETOOTHBCM),)第二个参数空就是NULL意思是(BOARD_HAVE_BLUETOOTH_BCM)的值不是NULL就可以进行下面的编译处理
$(BOARD_HAVE_BLUETOOTH_BCM)是获取make file中的环境变量或者宏定义 ---- BOARD_HAVE_BLUETOOTH_BCM的值。
要排除目录,请使用非路径选项。 例如,如果您正在替换本地 git repo 中的字符串,以排除所有以点(.)开头的文件 、使用:
find . -type f -not -path ‘/.’ -print0 | xargs -0 sed -i ‘s/foo/bar/g’
这里不是很理解,后面再补充。
这个命令前面我们也是用过多次,功能很简单就是将输入按照一定方式排序,然后再输出,它支持的排序有按字典排序,数字排序,按月份排序,随机排序,反转排序,指定特定字段进行排序等等。
默认为字典排序:
$ cat /etc/passswd | sort
反转排序:
$ cat /etc/passwd | sort -r
按特定字段排序:
$ cat /etc/passwd | sort -t’:’ -k 3
上面的-t参数用于指定字段的分隔符,这里是以":"作为分隔符;-k 字段号用于指定对哪一个字段进行排序。
那么,在Makefile中,.PHONY后面的target表示的也是一个伪造的target, 而不是真实存在的文件target,注意Makefile的target默认是文件。
所谓伪目标就是这样一个目标,它不代表一个真正的文件名,在执行make时可以指定这个目标来执行其所在规则定义的命令,有时我们将一个伪目标成为标签。
为什么要使用伪目标,一种为了避免在makefile中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突,另一种是提交执行makefile时的效率。
编写一个makefile同时编译、链接下面两个程序:
main1.c:
#include
int main(void)
{
printf("main1\n");
}
main2.c:
#include
int main(void)
{
printf("main2\n");
}
这里需要生成两个可执行文件main1和main2(两个目标)。由于makefile只能有一个目标,所以可以构造一个没有规则的终极目标all,并以这两个可执行文件作为依赖。如下:
makefile:
all:main1 main2
main1: main1.c
@gcc main1.c -o main1
main2: main2.c
@gcc main2.c -o main2
很多时候我们在执行make时会产生许多过程文件,比如将上面的makefile改为:
makefile:
all:main1 main2
main1: main1.c
@gcc main1.c -o main1
main2: main2.o
@gcc main2.o -o main2
main2.o: main2.c
@gcc -c main2.c
这样就会生成一个我们不需要的过程文件main2.o。
如果希望将生成的过程文件删掉,根据前面再增加一个目标clean:
all:main1 main2 clean
main1: main1.c
@gcc main1.c -o main1
main2: main2.o
@gcc main2.o -o main2
main2.o: main2.c
@gcc -c main2.c
clean:
@rm -f main2.o
但是当我们make之后main2.o仍然存在,原来这里的目标clean没有任何依赖,make执行时认为这已经到“根上”了(就是认为磁盘上有clean,就像main2.c),将其忽略(尽管它有规则)。
关键字.PHONY可以解决这问题,告诉make该目标是“假的”(磁盘上其实没有clean),这时make为生成这个目标就会将其规则执行一次。.PHONY修饰的目标就是只有规则没有依赖。
加上一句.PHONY:clean即可:
all:main1
main2 clean
main1: main1.c
@gcc main1.c -o main1
main2: main2.o
@gcc main2.o -o main2
main2.o: main2.c
@gcc -c main2.c
.PHONY:clean
clean: @rm -f main2.o
.PHONY : clean
这样执行"make clean"会无视"clean"文件存在与否。
静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法:
…
targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-parrtern是指明了targets的模式,也就是的目标集模式。
prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。
这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。
如果我们的定义成“%.o”,意思是我们的集合中都是以“.o”结尾的,
而如果我们的定义成“%.c”,意思是对所形成的目标集进行二次定义,
其计算方法是,取模式中的“%”(也就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾,形成的新集合。
所以,我们的“目标模式”或是“依赖模式”中都应该有“%”这个字符,如果你的文件名中有“%”那么你可以使用反斜杠“\”进行转义,来标明真实的“%”字符。
看一个例子:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
上面的例子中,指明了我们的目标从 o b j e c t 中 获 取 , “ object中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,也就是变量 object中获取,“object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foobar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.cbar.c”。
而命令中的“ < ” 和 “ <”和“ <”和“@”则是自动化变量,“ < ” 表 示 所 有 的 依 赖 目 标 集 ( 也 就 是 “ f o o . c b a r . c ” ) , “ <”表示所有的依赖目标集(也就是“foo.c bar.c”),“ <”表示所有的依赖目标集(也就是“foo.cbar.c”),“@”表示目标集(也褪恰癴oo.o bar.o”)。于是,上面的规则展开后等价于下面的规则:
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
试想,如果我们的“%.o”有几百个,那种我们只要用这种很简单的“静态模式规则”就可以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会一个很强大的功能。再看一个例子:
files = foo.elc bar.o lose.o
( f i l t e r (filter %.o, (filter(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
( f i l t e r (filter %.elc, (filter(files)): %.elc: %.el
emacs -f batch-byte-compile $<
( f i l t e r (filter%.o, (filter(files))表示调用Makefile的filter函数,过滤“$filter”集,只要其中模式为“%.o”的内容。其的它内容,我就不用多说了吧。这个例字展示了Makefile中更大的弹性。
$* 不包含扩展名的目标文件名称。
$+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件。
$< 第一个依赖文件的名称。
$? 所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚。
$@ 目标的完整名称。
$^ 所有的依赖文件,以空格分开,不包含重复的依赖文件。
$% 如果目标是归档成员,则该变量表示目标的归档成员名称。