前言
c项目的编译和构建
- 编译过程
- 静态函数库、共享函数库、动态加载函数库
- gcc
- cmake
- make
- gdb
编译过程
一个源代码程序经历预处理、编译、汇编以及链接这4个过程,最终生成对应的可执行程序。
文件名称
常见名称的含义
源文件(Source file):通常指存放可编辑代码的文件,例如存放C C++和汇编语言的文件。
目标文件(Object file) :指经过编译器的编译生成的CPU可识别的二进制代码,一般不能执行。
可执行文件(Excutable file):目标文件和相关的库链接后的文件,可执行
具体过程
预处理 : preprocessor
编译 : compiler 优化 : optimizer
汇编:assembler
链接 : linker
函数库
库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行。
静态函数库(static libraries):在程序执行前就加入到目标程序中去了
共享函数库(shared libraries):在程序启动的时候加载到程序中,它可以被不同的程序共享,可以同时被多个线程同时使用
动态加载函数库(dynamically loaded libraries):可以在程序运行的任何时候动态的加载
静态链接库
静态链接库名字一般是libxxx.a,是obj文件的集合,整个函数库都会被整合进目标程序中。
编译后的执行程序不需要外部的函数库支持,如果静态库改变了,必须重新编译。
静态链接库节省了编译时间,现在一般的程序的重新编译也花费不了多少时间,这个优势不再明显。
静态链接库提供给别人使用时,可以对函数的源代码保密,在某些时候对开发者来说还是很有用的。
理论上说,使用ELF格式的静态库生成的代码可以比使用共享函数库(或者动态函数库)的程序运行速度上快一些,大概1-5%。
共享函数库
共享函数库中的函数是在当一个可执行程序在启动的时候被加载,库中的函数和变量的地址是相对地址,真实地址在调用动态链接库加载时形成。
如果一个共享函数库正常安装,所有的程序在重新运行的时候都可以自动加载最新的函数库中的函数。
- 升级了函数库但是仍然允许程序使用老版本的函数库。
- 当执行某个特定程序的时候可以覆盖某个特定的库或者库中指定的函数。
- 可以在库函数被使用的过程中修改这些函数库。
命名
每个共享函数库都有个特殊的名字,别名:soname
每个共享函数库都有一个真正的名字,真名:real name
编译器编译的时候需要的函数库名字,链接名:linker name
soname
soname: “lib” + “函数库的名称” + “.so” + 版本号
例如:libtest.so.3的soname就是libtest.so.3。
特例,就是非常底层的C库函数都不是以lib开头这样命名的。
real name
real name有一个主版本号,和一个发行版本号(可选)。
real name是包含真正库函数代码的文件,主版本号和发行版本号使你可以知道你到底是安装了什么版本的库函数。
例如:libtest.so.3.0的real name就是libtest.so.3.0,这里有发行版本号。
linker name
linker name是编译器编译的时候需要的函数库的名字。
文件位置
共享函数库文件必须放在一些特定的目录里,这样通过系统的环境变量设置,应用程序才能正确的使用这些函数库。
GNU标准建议
所有的函数库文件都放在/usr/local/lib目录下,建议可执行程序都放在/usr/local/bin目录下。
大部分的源码开发的程序都遵循GNU的一些标准,查看info帮助:info:standards#Directory_Variables,这是一些习惯问题,可以改变。
FHS(Filesystem Hierarchy Standard)建议
在一个发行包中大部分的函数库文件应该安装到/usr/lib目录下,
在系统启动的时候要加载的库放到/lib目录下,
不是系统本身一部分的库则放到/usr/local/lib目录下。
GNU提出的标准主要对于开发者开发源码的,而FHS的建议则是针对发行版本的路径的。具体的位置信息可以看/etc/ld.so.conf里面的配置信息。
头文件路径和库文件路径是编译器默认查找的地方,例如
如何使用
在基于GNU glibc的系统里,包括所有的linux系统,启动一个ELF格式的二进制可执行文件会自动启动和运行一个program loader。对于Linux系统,这个loader的名字是/lib/ld-linux.so.X(X是版本号)。这个loader启动后,反过来就会load所有的其他本程序要使用的共享函数库。
到底在哪些目录里查找共享函数库呢?
定义缺省的是放在/etc/ld.so.conf文件里面,可以修改这个文件,加入自己的一些特殊的路径要求。
大多数RedHat系列的发行包的/etc/ld.so.conf文件里面不包括/usr/local/lib这个目录。
如果想用自己的函数替换某个库中的一些函数,同时保留该库中其他的函数的话,可以在 /etc/ld.so.preload中加入你想要替换的库(.o结尾的文件),这些preloading的库函数将有优先加载的权利。
高速缓冲
当程序启动的时候搜索所有的目录显然会效率很低,为了提高访问函数库的速度,使用了高速缓冲。
ldconfig缺省情况下读出/etc/ld.so.conf相关信息,然后设置适当地符号链接,然后写一个cache到/etc/ld.so.cache这个文件中,而这个/etc/ld.so.cache则可以被其他程序有效的使用了。
重新运行ldconfig来更新cache,通常的一些包管理器在安装一个新的函数库的时候就要运行ldconfig。
要新增一个动态加载的函数库
要删除某个函数库
要修改某个函数库的路径
环境变量
Linux系统中,环境变量LD_LIBRARY_PATH可以用来指定函数库查找路径的,这个路径通常是在查找标准的路径之前查找。
环境变量LD_PRELOAD列出了所有共享函数库中需要优先加载的库文件,功能和/etc/ld.so.preload类似。
LD_LIBRARY_PATH在开发和调试过程中经常大量使用,但是不应该被一个普通用户在安装过程中被安装程序修改。
事实上还有更多的环境变量影响着程序的调入过程,它们的名字通常就是以LD_或者RTLD_打头。
创建
标准格式: gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list
样例,其中使用-fPIC来产生代码,而不是-fpic,PIC的意思是“位置无关代码”(Position Independent Code)
1
2
3
4
5
6 #创建object文件a.o
#创建object文件b.o
#创建一个包含a.o和b.o的共享函数库
gcc –fPIC -g -c -Wall a.c
gcc –fPIC -g -c -Wall b.c
gcc -shared -Wl,-soname,liblusterstuff.so.1 -o liblusterstuff.so.1.0.1 a.o b.o -lc
安装
拷贝库文件到指定的标准的目录,然后运行ldconfig
修改环境变量
兼容
尽量用二进制接口ABI(Application Binary Interface)向上兼容
动态加载函数库
动态加载函数库可以在程序运行过程中的任何时间加载。
特别适合在函数中加载一些模块和plugin扩展模块的场合,因为它可以在当程序需要某个plugin模块时才动态的加载。
Linux系统下,动态加载函数库与其他函数库在格式上没有特殊的区别,创建的时候是标准的object格式。主要的区别就是这些函数库不是在程序链接的时候或者启动的时候加载,而是通过一个API来打开一个函数库,寻找符号表,处理错误和关闭函数库。
API
dlopen
dlerror
dlsym
dlclose
ldconfig命令
ldconfig是动态链接库的管理命令。
选项 | 含义 |
---|---|
-v, –verbose | 打印ldconfig当前版本号,显示所扫描的每一个目录和动态链接库 |
-n | |
-N | |
GCC
GCC是GNU Compiler Collection缩写,即GNU编译器套件。
一个源代码程序经历预处理、编译、汇编以及链接这4个过程,最终生成对应的可执行程序。
编译器
编译器命令 | 含义 |
---|---|
cc | 指的是C语言编译器 |
cpp | 指的是预处理编译器 |
gcc | 指的是C语言的编译器 |
g++ | 指的是C++语言的编译器 |
通常执行gcc指令编译C程序,通过g++指令编译C++程序
只要是GCC支持编译的程序代码,gcc指令根据后缀名自行判断所用的语言,完成编译。
gcc指令使用-x选项为用户提供了手动指定代表编译方式的方式
例如:gcc -xc xxx表示以编译C语言代码的方式编译xxx文件;而gcc -xc++ xxx 则表示以编译C++代码的方式编译xxx文件。
文件扩展名
扩展名 | GCC 编译器识别的文件类型 |
---|---|
*.c | 尚未经过预处理操作的 C 源程序文件。 |
*.i | 经过预处理操作、但尚未进行编译、汇编和连接的C源代码文件。 |
*.cpp *.cp *.cc *.cxx *.CPP *.c++ *.C | 尚未经过预处理操作的C++源代码文件。 |
*.ii | 已经预处理操作,但尚未进行编译、汇编和连接的 C++ 源代码文件。 |
*.s | 经过编译生成的汇编代码文件。 |
*.h | C、C++ 或者 Objective-C++ 语言头文件。 |
*.hh *.H *.hp *.hxx *.hpp *.HPP *.h++ *.tcc | C++ 头文件。 |
*.o | 汇编后的目标文件 |
a.out | 为链接后的输出文件 |
常用的编译选项
更多选项参考GCC手册
gcc/g++指令 | 功 能 |
---|---|
-E(大写) | 预处理指定的源文件,不进行编译。 |
-S(大写) | 编译指定的源文件,但是不进行汇编。 |
-c | 编译、汇编指定的源文件,但是不进行链接。 |
-o | 指定生成文件的文件名。 |
-ansi | 对于 C 语言程序来说,其等价于 -std=c90;对于 C++ 程序来说,其等价于 -std=c++98。 |
-std= | 手动指令编程语言所遵循的标准,例如 c89、c90、c++98、c++11 等。 |
-fPIC | 表示编译为位置独立的代码,用于编译共享库。 |
-llibrary(-I library) | 其中 library 表示要搜索的库文件的名称。该选项用于手动指定链接环节中程序可以调用的库文件。建议 -l 和库文件名之间不使用空格,比如 -lstdc++。 |
-l library | 指定链接时需要的动态库。编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a/.so来确定库的名称。 |
-L | 表示要连接的库所在的目录。 |
-Wall | 生成所有警告信息。 |
-Wl,options | 把参数(options)传递给链接器ld。如果options中间有逗号,就将options分成多个选项,然后传递给链接程序。 |
编译标准
不同版本的 GCC 编译器,默认使用的标准版本也不尽相同。
对于编译C、C++程序来说,借助-std选项即可手动控制GCC编译程序时所使用的编译标准。
基本格式
gcc/g++ -std=编译标准
C语言标准
C语言标准:C89(有时称为C90)、C94(C89的修订版)、C99、C11、C17。
GCC编译器本身还对C语言进行了扩展,先后产生了GNU90、GNU99、GNU11以及GNU17。
GCC版本 | c89/C90 | C99 | C11 | C17 | GNU90 | GNU99 | GNU11 | GNU17 |
---|---|---|---|---|---|---|---|---|
8.4~10.1 | c89/C90 | c99 | c11 | c17/c18 | gnu90/gnu89 | gnu99 | gnu11 | gnu17/gnu18 |
5.5~7.5 | c89/c90 | c99 | c11 | gnu90/gnu89 | gnu99 | gnu11 | ||
4.8.4~4.9.4 | c89/c90 | c99 | c11 | gnu90/gnu89 | gnu99 | gnu11 | ||
4.7.4 | c89/c90 | c99(部分) | c11(部分) | gnu90/gnu89 | gnu99(部分) | gnu11(部分) | ||
4.6.4 | c89/c90 | c99(部分) | c1x(部分) | gnu90/gnu89 | gnu99(部分) | gnu1x(部分) | ||
4.5.4 | c89/c90 | c99(部分) | gnu90/gnu89 | gnu99(部分) |
C++标准
C++标准:C++98、C++03(C++98的修订版)、C++11(有时又称为C++0x)、C++14、C++17。
GCC编译器本身对的C++标准做了相应的扩展,比如GNU++98、GNU++11、GNU++14、GNU++17。
GCC | C++98/03 | C++11 | C++14 | C++17 | GNU++98 | GNU++11 | GNU++14 | GNU++17 |
---|---|---|---|---|---|---|---|---|
8.4-10.1 | c++98/c++03 | c++11 | c++14 | c++17 | gnu++98/gnu++03 | gnu++11 | gnu++14 | gnu++17 |
5.5-7.5 | c++98/c++03 | c++11 | c++14 | c++1z部分 | gnu++98/gnu++03 | gnu++11 | gnu++14 | gnu++1z部分 |
4.8.4~4.9.4 | c++98/c++03 | c++11 | c++1y部分 | gnu++98/gnu++03 | gnu++11 | gnu++1y部分 | ||
4.7.4 | c++98 | c++11部分 | gnu++98 | gnu++11部分 | ||||
4.6.4 | c++98 | c++0x部分 | gnu++98 | gnu++0x部分 | ||||
4.5.4 | c++98 | c++0x部分 | gnu++98 | gnu++0x部分 |
安装的部分
部分 | 描述 |
---|---|
c++ | gcc 的一个版木,默认语言设置为C++,而且在连接的时候自动包含标准 C++ 库。这和 g++ 一样 |
ccl | 实际的C编译程序 |
cclplus | 实际的 C++ 编泽程序 |
collect2 | 在不使用 GNU 连接程序的系统上,有必要运行 collect2 来产生特定的全局初始化代码(例如 C++ 的构造函数和析构函数) |
configure | GCC 源代码树根目录中的一个脚木。用于设置配置值和创建GCC 编译程序必需的 make 程序的描述文件 |
crt0.o | 这个初始化和结束代码是为每个系统定制的,而且也被编译进该文件,该文件然后会被连接到每个可执行文件中来执行必要的启动和终止程序 |
cygwin1.dll | Windows 的共享库提供的 API,模拟 UNIX 系统调用 |
f77 | 该驱动程序可用于编译 Fortran |
f771 | 实际的 Fortran 编译程序 |
g++ | gcc 的一个版木,默认语言设置为 C++,而且在连接的时候自动包含标准 C++ 库。这和 c++ 一样 |
gcc | 该驱动程序等同于执行编译程序和连接程序以产生需要的输出 |
gcj | 该驱动程序用于编译Java |
gnat1 | 实际的 Ada 编译程序 |
gnatbind | 一种工具,用于执行 Ada 语言绑定 |
gnatlink | 一种工具,用于执行 Ada 语言连接 |
jc1 | 实际的 Java 编译程序 |
libgcc | 该库包含的例程被作为编泽程序的一部分,是因为它们可被连接到实际的可执行程序中。 它们是特殊的例程,连接到可执行程序,来执行基木的任务,例如浮点运算。这些库中的例程通常都是平台相关的 |
libgcj | 运行时库包含所有的核心 Java 类 |
libobjc | 对所有 Objective-C 程序都必须的运行时库 |
libstdc++ | 运行时库,包括定义为标准语言一部分的所有的 C++ 类和函数 |
安装的软件
工具 | 描述 |
---|---|
addr2line | 给出一个可执行文件的内部地址,addr2line 使用文件中的调试信息将地址翻泽成源代码文 件名和行号。该程序是 binutils 包的一部分 |
ar | 这是一个程序,可通过从文档中增加、删除和析取文件来维护库文件。通常使用该工具是为了创建和管理连接程序使用的目标库文档。该程序是 binutils 包的一部分 |
as | GNU 汇编器。实际上它是一族汇编器,因为它可以被编泽或能够在各种不同平台上工作。 该程序是 binutils 包的一部分 |
autoconf | 产生的 shell 脚木自动配置源代码包去编泽某个特定版木的 UNIX |
c++filt | 程序接受被 C++ 编泽程序转换过的名字(不是被重载的),而且将该名字翻泽成初始形式。 该程序是 binutils 包的一部分 |
f2c | 是 Fortran 到C的翻译程序。不是 GCC 的一部分 |
gcov | gprof 使用的配置工具,用来确定程序运行的时候哪一部分耗时最大 |
gdb | GNU 调试器,可用于检查程序运行时的值和行为 |
GNATS | GNU 的调试跟踪系统(GNU Bug Tracking System)。一个跟踪 GCC 和其他 GNU 软件问题的在线系统 |
gprof | 该程序会监督编泽程序的执行过程,并报告程序中各个函数的运行时间,可以根据所提供 的配置文件来优化程序。该程序是 binutils 包的一部分 |
ld | GNU 连接程序。该程序将目标文件的集合组合成可执行程序。该程序是 binutils 包的一部 |
libtool | 一个基本库,支持 make 程序的描述文件使用的简化共享库用法的脚木 |
make | 一个工具程序,它会读 makefile 脚木来确定程序中的哪个部分需要编泽和连接,然后发布 必要的命令。它读出的脚木(叫做 makefile 或 Makefile)定义了文件关系和依赖关系 |
nlmconv | 将可重定位的目标文件转换成 NetWare 可加载模块(NetWare Loadable Module, NLM)。该 程序是 binutils 的一部分 |
nm | 列出目标文件中定义的符号。该程序是 binutils 包的一部分 |
objcopy | 将目标文件从一种二进制格式复制和翻译到另外一种。该程序是 binutils 包的一部分 |
objdump | 显示一个或多个目标文件中保存的多种不同信息。该程序是 binutils 包的一部分 |
ranlib | 创建和添加到 ar 文档的索引。该索引被 Id 使用来定位库中的模块。该程序是 binutils 包的一部分 |
ratfor | Ratfor 预处理程序可由 GCC 激活,但不是标准 GCC 发布版的一部分 |
readelf | 从 ELF 格式的目标文件显示信息。该程序是 binutils 包的一部分 |
size | 列出目标文件中每个部分的名字和尺寸。该程序是 binutils 包的一部分 |
strings | 浏览所有类型的文件,析取出用于显示的字符串。该程序是 binutils 包的一部分 |
strip | 从目标文件或文档库中去掉符号表,以及其他调试所需的信息。该程序是 binutils 包的一部 |
vcg | Ratfor 浏览器从文木文件中读取信息,并以图表形式显示它们。而 vcg 工具并不是 GCC 发布中的一部分,但 -dv 选项可被用来产生 vcg 可以理解的优化数据的格式 |
windres | Window 资源文件编泽程序。该程序是 binutils 包的一部分 |
GCC实验
从源代码转变为可执行代码的过程,具体可分为 4 个过程:预处理、编译、汇编和链接。
预处理
预处理操作,主要是处理那些源文件和头文件中以#开头的命令(比如 #include、#define、#ifdef 等),并删除程序中所有的注释。
1 | //hello.c |
gcc -E 常用选项选 项 | 功 能 |
---|---|
-D name[=definition] | 在处理源文件之前,先定义宏 name。宏name必须是在源文件和头文件中都没有被定义过的。将该选项搭配源代码中的#ifdef name命令使用,可以实现条件式编译。如果没有指定一个替换的值(即省略 =definition),该宏被定义为值 1。 |
-U name | 如果在命令行或 GCC 默认设置中定义过宏name,则取消name 的定义。-D 和 -U 选项会依据在命令行中出现的先后顺序进行处理。 |
-include file | 如同在源代码中添加 #include file 一样。 |
-iquote dir | 对于以引号(#include “”)导入的头文件中,-iquote 指令可以指定该头文件的搜索路径。当GCC在源程序所在目录下找不到此头文件时,就会去-iquote指令指定的目录中查找。 |
-I dir | 同时适用于以引号 “” 和 <> 导入的头文件。当GCC在-iquote 指令指定的目录下搜索头文件失败时,会再自动去 -I 指定的目录中查找。该选项在 GCC10.1版本中已被弃用,并建议用 -iquote 选项代替。 |
-isystem dir | 用于指定搜索头文件的目录,适用于以引号 “” 和 <> 导入的头文件。 |
-idirafter dir | 用于指定搜索头文件的目录,适用于以引号 “” 和 <> 导入的头文件。 |
其中,对于指定 #include 搜索路径的几个选项,作用的先后顺序如下:
- 对于用 #include “” 引号形式引入的头文件,首先搜索当前程序文件所在的目录,其次再前往 -iquote 选项指定的目录中查找;
- 前往 -I 选项指定的目录中搜索;
- 前往 -isystem 选项指定的目录中搜索;
- 前往默认的系统路径下搜索;
- 前往 -idirafter 选项指定的目录中搜索。
编译
对已得到的预处理文件进行编译,将其再加工为相应的汇编文件。
1 | //hello.c |
汇编
汇编其实就是将汇编代码转换成可以执行的机器指令。大部分汇编语句对应一条机器指令,有的汇编语句对应多条机器指令。
1 | //hello.c |
链接
链接:把多个二进制的目标文件(object file)链接成一个单独的可执行文件。GCC的-l
选项可以让我们手动添加链接库。
标准库的大部分函数通常放在文件 libc.a中,或者放在用于共享的动态链接文件libc.so中,当使用GCC编译和链接程序时,GCC默认会链接 libc.a或者libc.so。
对于其他的库(例如非标准库、第三方库等),链接时需要手动添加。
另外,标准头文件math.h对应的数学库默认不会被链接,数学库的文件名是libm.a或libm.so,前缀lib
和后缀.a
或.so
是标准的,m
是基本名。
GCC会在
-l
选项后紧跟着的基本名称的基础上自动添加这些前缀、后缀
1 | //cos.c |
链接其它目录中的库
把链接库作为一般的目标文件,为GCC指定链接库的完整路径与文件名 gcc
libm.so在不同系统下文件位置可能不同,例如:
gcc cos.c /usr/lib/x86_64-linux-gnu/libm.so
使用
-L
选项,为GCC增加搜索链接库的目录。可以使用多个-L
选项,或者在一个-L
选项内使用冒号分割的路径列表。libm.so在不同系统下文件位置可能不同,例如:
gcc cos.c -o main.out -L/usr/lib/x86_64-linux-gnu -lm
把包括所需链接库的目录加到环境变量LIBRARYPATH中。
静态库
生成静态库或者将一个obj文件加入到静态库,命令是”ar 库文件 obj文件1 obj文件2”
帮助命令: ar –help
例如: 将string.打包为库文件libstr.a
$ar -rcs libstr.a string.o
共享的动态库
生成共享库
标准格式: gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list
cmake构建
常见的构建方式
todo 补充cmake生成库的构建样例
make
按照一个项目工程,集成make特性。项目地址:agui93-make-experiments
目录 | 功能 集成的特性 |
---|---|
step1 | 使用shell脚本 按步骤编译 |
step2 | 使用shell脚本 整体编译 |
step3 | make 简单规则 |
step4 | make 自定义变量 预定义变量 模式匹配 |
step5 | make 搜索路径 |
step6 | make 自动推导规则 |
step7 | make 递归 |
规则
make命令执行时先在Makefile文件中查找各种规则,对各种规则进行解析后运行规则。
规则的基本格式
1 | targets: prerequisites |
规则中的元素 | 含义 |
---|---|
TARGET | 规则所定义的目标 |
DEPENDEDS | 执行此规则所必须的依赖条件 |
COMMAND | 规则所执行的命令,即规则的动作 |
规则元素 | 含义 |
---|---|
目标 | 目标可以是具体的文件,也可以是某个动作 |
依赖项 | 依赖项是目标生成所必须满足的条件,即依赖项的动作必须在TARGET的命令之前执行。 依赖项之间的顺序按照自左向右的顺序检查或者执行 |
命令 | 命令行必须以Tab键开始,make程序把出现在一条规则之后的所有连续的以Tab键开始的行都作为命令行处理 |
反斜杠(\) | 用反斜杠(\)将较长的行分解为多行。 |
规则的嵌套 | 规则之间是可以嵌套的,通常通过依赖项实现 |
文件的时间戳 | make命令执行的时候会根据文件的时间戳判定是否执行相关的命令,并且执行依赖于此项的规则。 |
模式匹配 | %符号 *符号 |
预定义变量
变量 | 含义 | 默认值 |
---|---|---|
AR | 生成静态库库文件的程序名称 | ar |
AS | 汇编编译器的名称 | as |
CC | C语言编译器名称 | cc |
CPP | C语言预编译器的名称 | ${CC} -E |
CXX | C++语言编译器名称 | g++ |
FC | FORTRAN语言编译器的名称 | f77 |
RM | 删除程序文件的名称 | rm -f |
ARFLAGS | 生成静态库库文件程序的选项 | 无默认值 |
ASFLAGS | 汇编语言编译器的编译选项 | 无默认值 |
CFLAGS | C语言编译器的编译选项 | 无默认值 |
CPPFLAGS | C语言预编译的编译选项 | 无默认值 |
CXXFLAGS | C++语言编译器的编译选项 | 无默认值 |
FFLAGS | FORTRAN语言编译器的编译选项 | 无默认值 |
自动变量
变量 | 含义 |
---|---|
$* | 表示目标文件的名称,不包含目标文件的扩展名 |
$+ | 表示所有的依赖条件,这些依赖条件直接以空格分开,按照出现的先后为顺序,其中可能包含重复的依赖条件 |
$< | 表示依赖项中第一个依赖文件的名称 |
$? | 依赖项中,所有目标文件时间戳晚的依赖文件,依赖文件之间以空格分开 |
$@ | 目标项中目标文件的名称 |
$^ | 依赖项中,所有不重复的依赖条件,这些文件之间以空格分开 |
模式匹配
1 | targets ...: target-pattern: prereq-patterns ... |
The essence is that the given target is matched by the target-pattern (via a %
wildcard). Whatever was matched is called the stem. The stem is then substituted into the prereq-pattern, to generate the target’s prereqs.
演示:
1 | objects = foo.o bar.o all.o |
搜索路径
1 | vpath <pattern> <directories, space/colon separated> |
或者
1 | vpath = path1:path2:. |
自动推导规则
Perhaps the most confusing part of make is the magic rules and variables that are made. Here’s a list of implicit rules:
- Compiling a C program:
n.o
is made automatically fromn.c
with a command of the form$(CC) -c $(CPPFLAGS) $(CFLAGS)
- Compiling a C++ program:
n.o
is made automatically fromn.cc
orn.cpp
with a command of the form$(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
- Linking a single object file:
n
is made automatically fromn.o
by running the command$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)
the important variables used by implicit rules are:
CC
: Program for compiling C programs; default ccCXX
: Program for compiling C++ programs; default G++CFLAGS
: Extra flags to give to the C compilerCXXFLAGS
: Extra flags to give to the C++ compilerCPPFLAGS
: Extra flags to give to the C preprocessorLDFLAGS
: Extra flags to give to compilers when they are supposed to invoke the linker
演示:
1 | CC = gcc # Flag for implicit rules |
make递归
To recursively call a makefile, use the special $(MAKE)
instead of make
because it will pass the make flags for you and won’t itself be affected by them.
The export directive takes a variable and makes it accessible to sub-make commands.
make中的函数
获取匹配模式的文件名
1 | $(wilcard PATTERN) |
模式替换
1 | $(patsubst pattern,replacement,text) |
循环函数
1 | $(foreach VAR,LIST,TEXT) |
gdb
gdb命令
gdb待整理
https://www.kancloud.cn/wizardforcel/gdb-tips-100/146708
gdbinit
https://github.com/cyrus-and/gdb-dashboard https://www.helplib.com/GitHub/article_129194
https://www.cntofu.com/book/46/gdb/gdb_dashboard_debug_info_at_a_glance.md https://www.helplib.com/GitHub/article_129194
https://github.com/gdbinit/Gdbinit
https://github.com/longld/peda
堆栈查看; 汇编含义的解读; 寄存器查看; memeory查看
reference
《c和指针》
《LINUX网络编程第2版》