• Makefile 学习


    Makefile 学习

    1、静态模式

    1. objects = foo.o bar.o 
    2. all: $(objects) 
    3. $(CC) $(CFLAGS) -o -o $@ $^ 
    4. $(objects): %.o: %.
    5. $(CC) -c $(CFLAGS) $< -o $@ 

    上面的例子中,指明了我们的目标从$object中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,也就是变量$object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foobar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.cbar.c”。而命令中的“$<”和“$@”则是自动化变量,“$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是oo.o bar.o”)。于是,上面的规则展开后等价于下面的规则:

    1. foo.o : foo.c 
    2. $(CC) -c $(CFLAGS) foo.c -o foo.o 
    3. bar.o : bar.c 
    4. $(CC) -c $(CFLAGS) bar.c -o bar.o 

    这是一个进行了提升的列子:

    1. files = foo.elc bar.o lose.o 
    2. $(filter %.o,$(files)): %.o: %.
    3. $(CC) -c $(CFLAGS) $< -o $@ 
    4. $(filter %.elc,$(files)): %.elc: %.el 
    5. emacs -f batch-byte-compile $< 

    使用了filter进行单独过滤处理。


    2、特殊命令标记符号

    • -号,会标记所有后面的命令都是返回成功,也就是忽略失败。

    • @号,会在make的时候,只是执行命令,而不输出命令本身。

    • make的 -n 参数,加上这个参数之后,只会展开命令,而不真的执行,可以帮助调试脚本。

    • .PHONY:clean 用来定一个伪目标,比如这里定义了一个清理操作。

    • [Tab]键后面跟着的会被当作命令来执行。多个命令有依赖关系的时候,要写在一行,并且用分号分隔。

    • =、:=、?=、+=的区别,=可以在前面的代码用后面的变量,:=则不可以。?=被赋值的变量如果没有定义过,就是等于后面的值,如果定义过,则什么都不做。+=用于追加。

    • override 定义了一个覆盖操作。


    3、make的环境变量

    有两个变量,一个是SHELL,一个是 MAKEFLAGS ,这两个变量不管你是否export,其总是要传递到下层Makefile中,其他环境变量需要export 导出一下。但是make命令中的有几个参数并不往下传递,它们是“-C”,“-f”,“-h”“-o”和“-W”(有关Makefile参数的细节将在后面说明)
    MAKEFILES这个环境变量,执行make的时候,会进行一个类似include的动作。一般不建议使用.


    4、Makefile 的核心

    里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

    显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。

    隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。

    变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。

    文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。

    注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“#”。


    5、特殊表达式

    • 变量替换

    1. foo:=a.o b.o c.o 
    2. bar:=$(foo:.o=.c) 

    理解为bar的值等于,foo里面的值将.o替换为.c之后。

    • 变量再当变量

    1. x = y 
    2. y = z 
    3. a := $($(x)

    这个表达式a=的值等于z;

    • 多层变量配合函数

    1. x = variable1 
    2. variable2 := Hello 
    3. y = $(subst 1,2,$(x)
    4. z = y 
    5. a := $($($(z))) 

    “$($($(z)))”扩展为“$($(y))”,而其再次被扩展为“$($(subst 1,2,$(x)))”。$(x)的值是“variable1”,subst函数把“variable1”中的所有“1”字串替换成“2”字串,于是,“variable1”变成“variable2”,再取其值,所以,最终,$(a)的值就是$(variable2)的值—— “Hello”。

    • 变量名组合

    1. first_second = Hello 
    2. a = first 
    3. b = second 
    4. all = $($a_$b

    这里的“$a_$b”组成了“first_second”,于是,$(all)的值就是“Hello”。

    • “函数”和“条件语句”一同使用

    1. ifdef do_sort 
    2. func := sort 
    3. else 
    4. func := strip 
    5. endif 
    6.  
    7. bar := a d b g q
    8.  
    9. foo := $($(func) $(bar)) 

    这个示例中,如果定义了“do_sort”,那么:foo := $(sort a d b g q c),于是$(foo)的值就是“a b c d g q”,而如果没有定义“do_sort”,那么:foo := $(sort a d bg q c),调用的就是strip函数。

    • 某个目标的局部变量

    1. prog : CFLAGS = -g 
    2. prog : prog.o foo.o bar.o 
    3. $(CC) $(CFLAGS) prog.o foo.o bar.o 

    在这个示例中,不管全局的$(CFLAGS)的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则),$(CFLAGS)的值都是“-g”


    6、自动化变量

    • $@
      表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。

    • $%
      仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%“就是"bar.o”,"$@“就是"foo.a”。如果目标不是函数库文件(Unix下是
      [.a],Windows下是[.lib]),那么,其值为空。

    • $<
      依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。

    • $?
      所有比目标新的依赖目标的集合。以空格分隔。

    • $^
      所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。

    • $+
      这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。

    • $*
      这个变量表示目标模式中"%“及其之前的部分。如果目标是"dir/a.foo.b”,并且目标的模式是"a.%.b",那么,"$“的值就是"dir /a.foo”。这个变量对于构造有关联的文件名是比较有较。
      如果目标中没有模式的定义,那么"$
      “也就不能被推导出,但是,如果目标文件的后缀是 make所识别的,那么”$“就是除了后缀的那一部分。
      例如:如果目标是"foo.c”,因为".c"是make所能识别的后缀名,所以,"$
      “的值就是"foo”。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用"$“,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么”$"就是空值。

    • $(@D)
      表示"$@“的目录部分(不以斜杠作为结尾),如果”$@“值是"dir/foo.o”,那么"$(@D)“就是"dir”,而如果"$@“中没有包含斜杠的话,其值就是”."(当前目录)。

    • $(@F)
      表示"$@“的文件部分,如果”$@“值是"dir/foo.o”,那么"$(@F)“就是"foo.o”,"$(@F)“相当于函数”$(notdir $@)"。

    • "$(*D)" "$(*F)"
      和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子,"$(*D)“返回"dir”,而"$(*F)“返回"foo”

    • "$(%D)" "$(%F)"
      分别表示了函数包文件成员的目录部分和文件部分。这对于形同"archive(member)"形式的目标中的"member"中包含了不同的目录很有用。

    • "$(<D)" "$(<F)"
      分别表示依赖文件的目录部分和文件部分。

    • "$(^D)" "$(^F)"
      分别表示所有依赖文件的目录部分和文件部分。(无相同的)

    • "$(+D)" "$(+F)"
      分别表示所有依赖文件的目录部分和文件部分。(可以有相同的)

    • "$(?D)" "$(?F)"
      分别表示被更新的依赖文件的目录部分和文件部分。

    最后想提醒一下的是,对于"$<",为了避免产生不必要的麻烦,我们最好给$后面的那个特定字符都加上圆括号,比如,"$(< )“就要比”$<"要好一些。

    还得要注意的是,这些变量只使用在规则的命令中,而且一般都是"显式规则"和"静态模式规则"(参见前面"书写规则"一章)。其在隐含规则中并没有意义。

    7、gcc 与 g++ 的区别

    误区一:gcc只能编译c代码,g++只能编译c++代码

    两者都可以,但是请注意:

    • 1.后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的,例如:

    1. int main(int argc, char* argv[])
    2. if(argv == 0) return
    3. printString(argv); 
    4. return
    5. int printString(char* string)
    6. sprintf(string, "This is a test. "); 

    如果按照C的语法规则,OK,没问题,但是,一旦把后缀改为cpp,立刻报三个错:

    1. “printString未定义”; 
    2. “cannot convert `char**' to `char*”; 
    3. return-statement with no value“; 

    分别对应前面红色标注的部分。可见C++的语法规则更加严谨一些。

    • 2.编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的。

    误区二:gcc不会定义__cplusplus宏,而g++会
    实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。

    误区三:编译只能用gcc,链接只能用g++
    严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。

    误区四:extern "C"与gcc/g++有关系
    实际上并无关系,无论是gcc还是g++,用extern "c"时,都是以C的命名方式来为symbol命名,否则,都以c++方式命名。

    8、gcc和g++的包含头文件库文件方法

    • -l参数 和 -L参数
      就是用来指定程序要链接的库,-l参数紧接着就是库名。
      -L参数跟着的是库文件所在的目录名

    • -include和-I参数
      -include用来包含头文件,但一般情况下包含头文件都在源码里用#include xxxxxx实现。
      -I参数是用来指定头文件目录,/usr/include目录一般是不用指定的,gcc知道去那里找.

    9、gcc中include文件的搜索路径

    #include有两种形式,例如如下:

    1. #include <syshead.h> 
    2. #include "userhead.h" 

    用尖括号表示的是包含系统的头文件,用双引号包含的是用户自己的头文件。

    下面是使用#include时的一些规则:
    1)使用<>包含的头文件一般会先搜索-I选项后的路径(即用gcc编译时的-I选项),之后就是标准的系统头文件路径。
    2)而用""号包含的头文件会首先搜索当前的工作目录,之后的搜索路径才是和<>号包含的头文件所搜索的路径一样的路径。
    3)在unix系统中,一般标准的头文件路径为:

    1. /usr/local/include 
    2. /usr/lib/gcc-lib/target/version/include 
    3. /usr/target/include 
    4. /usr/include 

    4)一般有两条独立的头文件搜索路径链。一条是-I后面指示的路径,另一条是系统头文件路径和以-prefix, -withprefix,和-idirafter后操作的目录。
    5)如果gcc编译的是c++的程序,那么在搜索上面所说的目录前,预处理器会首先搜索/usr/include/g++v3目录,v3是你的gcc中c++的版本。
    6)在头文件中运行增加路径名,例如:#include <sys/time.h>,那么就会在搜索的系统目录的sys目录下寻找time.h文件。
    7)一般会用斜线来作为目录的分割符,甚至有些系统使用不同的字符作为分割符(例如反斜线)。

    相关环境变量
    有大量的环境变量可供设置以影响 GCC 编译程序的方式。利用这些变量的控制也可使用合适的命令行选项。一些环境变量设置在目录名列表中。这些名字和 PATH 环境变量使用的格式相同。特殊字符 PATH_SEPARATOR (安装编译程序的时候定义)用在目录名之间。在 UNIX 系统中,分隔符是冒号,而 Windows 系统中为分号。
    C_INCLUDE_PATH
    编译 C 程序时使用该环境变量。该环境变量指定一个或多个目录名列表,查找头文件,就好像在命令行中指定 -isystem 选项一样。会首先查找 -isystem 指定的所有目录。
    ==> 也见 CPATH 、 CPLUS_INCLUDE_PATH 和 OBJC_INCLUDE_PATH 。

    COMPILER_PATH
    该环境变量指定一个或多个目录名列表,如果没有指定 GCC_EXEC_PREFIX 定位子程序,编译程序会在此查找它的子程序。
    ==> 也见 LIBRARY_PATH 、 GCC_EXEC_PREFIX 和 -B 命令行选项。

    CPATH
    编译 C 、 C++ 和 Objective-C 程序时使用该环境变量。该环境变量指定一个或多个目录名列表,查找头文件,就好像在命令行中指定 -l 选项一样。会首先查找 -l 指定的所有目录。
    ==> 也见 C_INCLUDE_PATH 、 CPLUS_INCLUDE_PATH 和 OBJC_INCLUDE_PATH 。

    CPLUS_INCLUDE_PATH
    编译 C++ 程序时使用该环境变量。该环境变量指定一个或多个目录名列表,查找头文件,就好像在命令行中指定 -isystem 选项一样。会首先查找 -isystem 指定的所有目录。
    ==> 也见 CPATH 、 C_INCLUDE_PATH 和 OBJC_INCLUDE_PATH 。

    DEPENDENCIES_OUTPUT
    为文件名设置该环境变量会让预处理程序将基于依赖关系的 makefile 规则写入文件。不会包括系统头文件名字。
    如果环境变量设置为单名,被看作是文件名字,而依赖关系规则的名字来自源文件名字。如果定义中有两个名字,则第二个名字是用作依赖关系规则的目标名。 设置该环境变量的结果和使用命令行选项 -MM 、 -MF 和 -MT 的组合是一样的。
    ==> 也见 SUNPRO_DEPENDENCIES 。

    GCC_EXEC_PREFIX
    如果定义了该环境变量,它会作为编译程序执行的所有子程序名字的前缀。例如,如果将变量设置为 testver 而不是查找 as ,汇编器首先会在名字 testveras 下查找。如果在此没有找到,编译程序会继续根据它的普通名进行查找。可在前缀名中使用斜线指出路径名。
    GCC_EXEC_PREFIX 的默认设置为 prefix /lib/gcc-lib/ ,这里的 prefix 是安装编译程序时 configure 脚本指定的名字。该前缀也用于定位标准连接程序文件,包含进来作为可执行程序的一部分。
    如果使用 -B 命令行选项,会重写该设置。
    ==> 也见 COMPILER_PATH 。

    9、gcc 常见意外处理

    warning: "unused parameter xxxx"警告

    • 第一种方法

    1. #define UNUSED(x) (void)x  
    2. void SomeFunction(int param1, int param2)  
    3. {  
    4. UNUSED(param2);  
    5. // do stuff with param1  

    在UNUSED(param2)语句不产生任何目标代码,消除对未使用的变量的警告,并明确文件,不要使用变量的代码。

    • 第二种方法
      warning: unused parameter ‘mcb’
      举例:

    1. int ifnMenuQuit(MCB_T *mcb) 
    2. return QUIT

    说明:因为函数参数中的mcb,在该函数中没有被使用,所以产生warning
    修改:对没使用的参数使用 para=para;

    1. int ifnMenuQuit(MCB_T *mcb) 
    2. mcb=mcb; <----------添加该行 
    3. return QUIT
    • 最优美的方法

    1. #ifdef UNUSED 
    2. #elif defined(__GNUC__) 
    3. # define UNUSED(x) UNUSED_ ## x __attribute__((unused)) 
    4. #elif defined(__LCLINT__) 
    5. # define UNUSED(x) /*@unused@*/ x 
    6. #else 
    7. # define UNUSED(x) x 
    8. #endif 
    9.  
    10. void dcc_mon_siginfo_handler(int UNUSED(whatsig)) 

    杂记

    -fpic告诉编译器将源代码编译成共享的object文件,PIC(Position-Independent Code)意思是函数都是相对地址,这是共享库所需要的。

    编译时”-lsum“的方式,是不能够区分当前是静态链接还是动态链接的。如果在同一个目录下同时有静态链接库和动态链接库,则系统默认会引用动态链接库,如果想使用静态链接库则需要在编译时加上”-static“参数(具体方法可自行百度

    共享库,有两种形式,第一种就是在上一篇文章中说到的“动态链接库”,而共享库的另一种形式,则被称之为“动态加载库”,也就是我刚才提到的用“dlopen”方式来玩的。动态加载库在编译的时候,应该是不需要去-l引用lib,而是在可执行程序中,可以自已决定加载库的时机。比如程序跑着跑着,突然想用libabc.so库里的一个叫abc的函数了,这时就可以用dlopen去打开这个库,然后使用dlsym去找到abc的函数指针并调用即可。

    编译时-rdynamic用来通知链接器将所有符号添加到动态符号表中(目的是能够通过使用 dlopen 来实现向后跟踪)。-ldl 表明一定要将 dllib 链接于该程序

    -MMD -MP -MF ./obj/local/armeabi/objs/jvm/jni-jvm.o.d
    生成依赖说明文件。

    -ffunction-sections, -fdata-sections会使compiler为每个function和data item分配独立的section。 --gc-sections会使ld删除没有被使用的section。

    -fstack-protector:
    启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码。

    -no-canonical-prefixes 生成其他gcc 组件的相对路径时不生成规范化的

    -march=armv5te
    -march 指定的指令集的版本 指定架构

    hard-float 是直接生成浮点运算的指令(如果有的话);soft-float 是用库模拟浮点运算(如果有的话)。

    -mthumb参数是编译全编译成thumb指令集,-mthumb-interwork是指thumb的code用thumb指令集,其他的用arm指令集

    -fomit-frame-pointer 编译选项
    忽略帧指针。这样在程序就不需要保存,安装,和恢复ebp了。这样ebp也就是一个
    free的register了,在函数中就可以随便使用了。

    -fno-strict-aliasing,则编译器认为任何 指针都可能指向同一个内存区域。因此对*b赋值,编译器认为有可能会影响a里面的值了。所以编译器给printf那一行传递参数的时候就认为寄存器里的 a[0],a[1]值已经不一定正确了,只有内存里的才可靠,于是只能老老实实从栈里取值了。

    -finline-limit=n对伪指令数超过n的函数,编译程序将不进行内联展开

    加上GCC編譯參數–noexecstack , 用以讓程式載入後Stack會透過MMU設定不可執行. (Hardware-based No eXecute (NX)),如此可避免像是透過Stack-Overflow將程式碼注入的軟體攻擊問題.

    ‘-Wformat’ This option warns about the incorrect use of format strings in functions
    such as printf and scanf, where the format specifier does not agree with the
    type of the corresponding function argument.

    由于Android 的gdbserver 会让程序停在linker 的启动进程。所以一般都是显示??,如果需要查看可以
    set sysroot adl_src/out/target/product/generic/symbols/
    或者,直接break main 下断点,然后continue执行过去。

  • 相关阅读:
    linux远程桌面连接 VNC Server
    linux内核 mtd分区
    STC15控制数码管 38译码器
    DS12C887实时时钟
    printf打印字节调试
    LED 控制卡 单元板 接口引脚定义
    linux守护进程start-stop-daemon启动服务
    相机速率计算
    CodeWarrior IDE烧写介绍
    让 Web 站点崩溃最常见的七大原因
  • 原文地址:https://www.cnblogs.com/rexonor/p/4876267.html
Copyright © 2020-2023  润新知