• gcc工具链——动态库和静态库,makefile,cmake,环境变量


    在这里插入图片描述

    gcc工具链——动态库和静态库,makefile,cmake,环境变量

    文章目录

    1.编译过程

    在这里插入图片描述

    1.1.预处理

    hello.c文件
    在这里插入图片描述
    在这里插入图片描述

    gcc -E hello.c -o hello.i

    hello.i文件(不到10行的代码文件预处理完了变成800多行)
    在这里插入图片描述
    在这里插入图片描述

    预处理就是头文件直接展开,宏定义直接替换,条件编译处理
    在这里插入图片描述

    1.2.编译

    编译就是先进行语法检查,语法没问题后将预处理后的文件转换为汇编文件

    gcc -S hello.i -o hello.s

    hello.s文件

    在这里插入图片描述

    1.3.汇编

    gcc -c hello.s -o hello.o

    hello.o文件(二进制文件,ELF开头)

    在这里插入图片描述
    在这里插入图片描述

    1.4.1.链接(动态链接)

    gcc hello.o -o hello

    该步骤将printf函数在哪的位置信息给链接进来

    1.4.2.链接(静态链接)

    gcc hello.o -o hello -static

    在这里插入图片描述

    静态链接文件要比动态链接文件大很多很多

    2.编译时常用的参数

    在这里插入图片描述

    3.1.静态库的制作例一

    准备材料:3个文件

    文件一:hello.c(我们公司自己写的hello.c)

    在这里插入图片描述

    文件二:f1.c(其他公司的机密源文件)

    在这里插入图片描述

    文件二:f2.c(其他公司的机密源文件)

    在这里插入图片描述

    其他公司不能把机密源文件给我们,因此要用库的形式给我们

    step1:将f1.c、f2.c这两个源文件制作成二进制文件

    gcc -c f1.c f2.c

    在这里插入图片描述

    step2:制作库文件

    ar -crv libx.a f1.o f2.o

    在这里插入图片描述

    lib开头,.a是静态库文件后缀,x是自己命名的库名字

    在这里插入图片描述

    libx.a就是我们制作的静态库文件

    若我们直接编译hello.c文件,则会跳出错误,找不到f1,f2函数:

    在这里插入图片描述

    正确做法

    gcc hello.c -o hello -static -L . -l x

    编译并且运行:

    在这里插入图片描述

    -L表示库所在路径,这里是在当前路径(.),-l表示库名,这里是x

    3.2.动态库的制作例一

    在这里插入图片描述

    准备材料:3个文件
    在这里插入图片描述

    制作:

    gcc -fPIC -shared -o libxx.so f1.c f2.c

    生成动态库文件libxx.so

    lib开头,.so是动态库文件后缀,xx是自己命名的库名字

    在这里插入图片描述

    编译并运行:

    gcc hello.c -o hello -L . -l xx

    在这里插入图片描述

    运行报错,因为它去找这个动态库文件是从默认路径去找

    在这里插入图片描述

    查看它去找的默认动态库路径ldd

    在这里插入图片描述

    发现它去/lib/x86_64-linux-gun/下去找这个动态库文件了

    所以我们把这个动态库文件拷贝到那个路径下再运行就可以了

    cp libxx.so /lib/x86_64-linux-gun/
    在这里插入图片描述

    4.1.静态库的制作例二

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    4.2.动态库的制作例二

    在这里插入图片描述

    在这里插入图片描述

    5.1.库的作用

    代码解耦,重用
    对外提供服务,典型应用:exe调用dll。我们打开软件目录,一堆dll,少量exe就是这个道理

    5.2.库的分类

    静态库:与exe打包成exe(一个文件),windows就是.lib文件,linux就是.a文件。
    动态库(共享库):与exe是独立的2个文件,windows就是.dll文件,linux就是.so文件

    5.3.动态库和静态库区别:

    在这里插入图片描述

    在这里插入图片描述

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jCnSPvLq-1663503981206)(photo/gcc工具链——动态库和静态库,makefile,cmake,环境变量-photo/image-20220918172212976.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RiApjLRb-1663503981206)(photo/gcc工具链——动态库和静态库,makefile,cmake,环境变量-photo/image-20220918173744291.png)]

    在这里插入图片描述
    在这里插入图片描述

    6.ldd命令

    wiki解释:Ldd(List Dynamic Dependencies,意译为列出动态库依赖关系)是一款在类Unix系统的实用工具,负责在命令行内输出程序或共享库所依赖的函数库。此工具由罗兰·麦克格拉斯及乌尔里希·德雷佩尔开发。Ldd在指定的程序缺少部分函数库的情况下将无法显示结果。
    ————————————————

    在这里插入图片描述

    ldd显示可执行模块的dependency的工作原理,其实质是通过ld-linux.so(elf动态库的装载器)来实现的。我们知道,ld-linux.so模块会先于executable模块程序工作,并获得控制权,因此当上述的那些环境变量被设置时,ld-linux.so选择了显示可执行模块的dependency。实际上可以直接执行ld-linux.so模块,如:/lib/ld-linux.so.2 --list program(这相当于ldd program)

    在这里插入图片描述

    在这里插入图片描述

    7.which 命令和ldd

    which:which查找文件与find方式不同,which从环境变量文件(/etc/profile)中的path目录中查找,且which找的都是可执行文件。

    在这里插入图片描述

    8.whereis 命令和ldd

    whereis:与which类似查询可执行文件,whereis查询更多,会查找出相关的man文件
    在这里插入图片描述

    9.located命令

    locate:这是区别以上两种的方式的一种查找文件方式,locate以一个数据库文件为基础查找。linux系统每天至少自动扫描一次文件,将结果保存到数据库,locate查的是数据库记录。locate查询比find要快,但最新文件如果在数据库中没有记录,会查询不到,次是需要手动更新(updatedb)

    10.which,whereis,locate,find的区别

    1. which:常用于查找可直接执行的命令。只能查找可执行文件,该命令基本只在$PATH路径中搜索,查找范围最小,查找速度快。默认只返回第一个匹配的文件路径,通过选项 -a 可以返回所有匹配结果。
    2. whereis:不只可以查找命令,其他文件类型都可以(man中说只能查命令、源文件和man文件,实际测试可以查大多数文件)。在$PATH路径基础上增加了一些系统目录的查找,查找范围比which稍大,查找速度快。可以通过 -b 选项,限定只搜索二进制文件。
    3. locate:超快速查找任意文件。它会从linux内置的索引数据库查找文件的路径,索引速度超快。刚刚新建的文件可能需要一定时间才能加入该索引数据库,可以通过执行updatedb命令来强制更新一次索引,这样确保不会遗漏文件。该命令通常会返回大量匹配项,可以使用 -r 选项通过正则表达式来精确匹配。
    4. find:直接搜索整个文件目录,默认直接从根目录开始搜索,建议在以上命令都无法解决问题时才用它,功能最强大但速度超慢。除非你指定一个很小的搜索范围。通过 -name 选项指定要查找的文件名,支持通配符。

    下面通过一个实际的例子来测试和体会几个命令的差异:

    先通过which找到ls命令的位置

    tarena@tedu:/$ which ls
    /bin/ls
    
    • 1
    • 2

    把ls复制到主目录,并把名称修改为newls

    tarena@tedu:/$ cp /bin/ls ~/newls
    tarena@tedu:/$ cd ~
    
    • 1
    • 2

    尝试用which和whereis命令查找newls,由于主目录不在 P A T H 中(除非你恰巧之前你恰巧把~加入 PATH中(除非你恰巧之前你恰巧把~加入 PATH中(除非你恰巧之前你恰巧把~加入PATH了),所以都无法找到

    tarena@tedu:~$ whereis newls
    newls:
    tarena@tedu:~$ which newls
    tarena@tedu:~$ 
    
    • 1
    • 2
    • 3
    • 4

    执行以下export命令,把~加入$PATH,然后我们cd到根目录,再次尝试查找newls,发现已经可以找到了

    tarena@tedu:~$ export PATH=$PATH:~
    tarena@tedu:~$ echo $PATH
    /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/tarena
    tarena@tedu:~$ cd /
    tarena@tedu:/$ which newls
    /home/tarena/newls
    tarena@tedu:/$ whereis newls
    newls: /home/tarena/newls
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们再cd到~,然后取消newls的可执行权限

    tarena@tedu:/$ cd ~
    tarena@tedu:~$ chmod u-x newls
    
    • 1
    • 2

    然后我们再次尝试使用which和whereis查找newls,我们发现whereis可以找到,而which找不到newls。因为which只能用来查找可执行文件,whereis没有该限制。

    tarena@tedu:~$ cd /
    tarena@tedu:/$ whereis newls
    newls: /home/tarena/newls
    tarena@tedu:/$ which newls
    
    • 1
    • 2
    • 3
    • 4

    这时我们再把newls改名为ls,然后我们尝试用locate命令找出系统中存在的两个ls文件,我们发现会找到大量不是我们要的文件(此处已省略了很多),但这些文件路径中确实包含ls。

    tarena@tedu:~$ cd ~
    tarena@tedu:~$ mv newls ls
    /bin/false
    /bin/ls
    /bin/lsblk
    /bin/lsmod
    /bin/ntfsls
    /boot/grub/i386-pc/cbls.mod
    /boot/grub/i386-pc/command.lst
    /boot/grub/i386-pc/crypto.lst
    /boot/grub/i386-pc/fs.lst
    /boot/grub/i386-pc/ls.mod
    /boot/grub/i386-pc/lsacpi.mod
    /boot/grub/i386-pc/lsapm.mod
    /boot/grub/i386-pc/lsmmap.mod
    /boot/grub/i386-pc/lspci.mod
    /boot/grub/i386-pc/moddep.lst
    /boot/grub/i386-pc/partmap.lst
    /boot/grub/i386-pc/parttool.lst
    /boot/grub/i386-pc/terminal.lst
    /boot/grub/i386-pc/video.lst
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    我们尝试用正则表达式缩小匹配范围

    tarena@tedu:~$ locate -r '\bls$'
    /bin/ls
    /usr/bin/gvfs-ls
    /usr/lib/klibc/bin/ls
    /usr/share/bash-completion/completions/gvfs-ls
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们发现只找到了一个ls,另外一个可能因为系统还没有纳入索引数据库,所以没有找到,我们执行updatedb命令,强制更新一下系统索引,然后再执行一遍locate试试,发现现在可以找到了

    tarena@tedu:~$ sudo updatedb
    /bin/ls
    /home/tarena/ls
    /usr/bin/gvfs-ls
    /usr/lib/klibc/bin/ls
    /usr/share/bash-completion/completions/gvfs-ls
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    find命令全盘查找太慢,所以限制下查找路径,也是同样可以找到

    tarena@tedu:~$ find ~ /bin/ -name ls
    /home/tarena/ls
    /bin/ls
    
    • 1
    • 2
    • 3

    11.库的安装

    在这里插入图片描述
    在这里插入图片描述

    12.ldconfig命令

    1.简介:

    ldconfig 命令用于在默认搜寻目录 /lib 和 /usr/lib 以及动态库配置文件 /etc/ld.so.conf 内所列的目录下,搜索出可共享的动态链接库(格式如 lib.so),进而创建出动态链接器(ld.so 或 ld-linux.so)所需的缓存文件。缓存文件默认为 /etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表。为了让动态链接库为系统所共享,需运行动态链接库的管理命令 ldconfig 更新动态链接库的缓存文件。**

    ldconfig 位于 /sbin 目录下,通常在系统启动时运行,当用户安装了一个新的动态链接库时,需要手动运行这个命令。

    2.常用示例:
    在这里插入图片描述

    3.注意事项:

    在这里插入图片描述

    13.Linux动态库路径查找杂谈 /etc/ld.so.conf

    Linux 系统上有两类不同的可执行程序。第一类是静态链接的可执行程序。静态可执行程序包含执行所需的所有函数和数据 — 换句话说,它们是“完整的”。因为这一原因,静态可执行程序不依赖任何外部库就可以运行。

    第二类是动态链接的可执行程序,这意味着它需要依赖外部库才可以运行。

    动态链接依赖查看

    我们可以用 ldd 命令来确定某一特定可执行程序是否为动态链接,如果是动态链接,还会打印出所依赖的动态库。

    victory@ubuntu:~$ ldd `which ls`
     linux-vdso.so.1 (0x00007ffc63f5c000)
     libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f4521458000)
     libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4521230000)
     libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007f4521198000)
     /lib64/ld-linux-x86-64.so.2 (0x00007f45214c8000)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意:ldd后面必须跟程序的绝对路径。

    我们自己来编译一个静态链接和动态链接的C程序:

    victory@ubuntu:~$ cat main.c
    int main() {
     return 0;
    }
    
    • 1
    • 2
    • 3
    • 4

    gcc编译默认是动态链接:

    victory@ubuntu:~$ gcc main.c
    victory@ubuntu:~$ ldd a.out 
     linux-vdso.so.1 (0x00007ffc3adfc000)
     libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbbd0740000)
     /lib64/ld-linux-x86-64.so.2 (0x00007fbbd0988000)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    通过–static选项可以静态编译:

    victory@ubuntu:~$ gcc main.c --static -o a.out.static
    victory@ubuntu:~$ 
    victory@ubuntu:~$ ldd a.out.static 
     not a dynamic executable
    
    • 1
    • 2
    • 3
    • 4

    也可以通过file命令查看程序类型:

    victory@ubuntu:~$ file a.out 
    a.out: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=d986d2fd8d2dce338f289b4c07dc0753e64c296d, for GNU/Linux 3.2.0, not stripped
    
    • 1
    • 2

    看下动态和静态链接的程序大小:

    victory@ubuntu:~$ ls -al a.out* -h
    -rwxrwxr-x 1 victory victory  16K Feb  5 22:45 a.out
    -rwxrwxr-x 1 victory victory 869K Feb  5 22:46 a.out.static
    
    • 1
    • 2
    • 3

    如您所见,a.out.static 的大小超过动态链接 a.out 十倍以上。

    动态装载器

    如果动态可执行程序不包含运行所需的所有函数和数据,Linux 的哪部分负责将这些程序和所有必需的共享库一起装入,以使它们能正确执行呢?

    答案是动态装入器(dynamic loader),它实际上是您在a.out 的 ldd 清单中看到的作为共享库依赖出的 ld-linux.so.2 库。动态装入器负责装入动态链接的可执行程序运行所需的共享库。现在,让我们迅速查看一下动态装入器如何在系统上找到适当的共享库。

    动态装载器找到共享库要依靠两个文件 — /etc/ld.so.conf/etc/ld.so.cache。如果您对 /etc/ld.so.conf 文件进行 cat 操作,您可能会看到一个与下面类似的清单:

    $ cat /etc/ld.so.conf
    /usr/X11R6/lib
    /usr/lib/gcc-lib/i686-pc-linux-gnu/2.95.3
    /usr/lib/mozilla
    /usr/lib/qt-x11-2.3.1/lib
    /usr/local/lib
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ld.so.conf 文件包含一个所有目录(/lib 和 /usr/lib 除外,它们会自动包含在其中)的清单,动态装入器将在其中查找共享库。

    在我的ubuntu系统上,ld.so.conf文件并没有直接包含库目录,而是include了/etc/ld.so.conf.d目录下以conf为后缀的文件:

    victory@ubuntu:~$ cat /etc/ld.so.conf
    include /etc/ld.so.conf.d/*.conf
    victory@ubuntu:~$ ls /etc/ld.so.conf.d/
    libc.conf  x86_64-linux-gnu.conf
    victory@ubuntu:~$ 
    victory@ubuntu:~$ cat /etc/ld.so.conf.d/libc.conf 
    # libc default configuration
    /usr/local/lib
    victory@ubuntu:~$ cat /etc/ld.so.conf.d/x86_64-linux-gnu.conf 
    # Multiarch support
    /usr/local/lib/x86_64-linux-gnu
    /lib/x86_64-linux-gnu
    /usr/lib/x86_64-linux-gnu
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上面的路径清单是系统自己添加的,也就是说只要我们把自己的动态库放到上述路径下,动态装载器就能找到。

    ld.so.cache

    但是在动态装入器能“看到”这一信息之前,必须将它转换到 ld.so.cache 文件中。可以通过运行 ldconfig 命令做到这一点:

    $ ldconfig
    
    • 1

    当 ldconfig 操作结束时,将会生成一个新的 /etc/ld.so.cache 文件,它反映您对 /etc/ld.so.conf 所做的更改。从这一刻起,动态装入器在寻找共享库时会查看您在 /etc/ld.so.conf 中指定的所有新目录。

    因此当安装完一些库文件,或者修改/etc/ld.so.conf增加了库的新的搜索路径,需要运行一下ldconfig,使所有的库文件都被缓存到文件/etc/ld.so.cache中,如果没做,可能会找不到刚安装的库。

    ldconfig 技巧

    -p选项可以查看ld.so.cache缓存的所有共享库:

    victory@ubuntu:~$ ldconfig -p
    1064 libs found in cache `/etc/ld.so.cache'
     libzvbi.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libzvbi.so.0
     libzvbi-chains.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libzvbi-chains.so.0
     libzstd.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libzstd.so.1
     libzmq.so.5 (libc6,x86-64) => /lib/x86_64-linux-gnu/libzmq.so.5
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    还有另一个方便的技巧可以用来配置共享库路径。有时候您希望告诉动态装入器在尝试任何 /etc/ld.so.conf 路径之前先尝试使用特定目录中的共享库。在您运行的较旧的应用程序不能与当前安装的库版本一起工作的情况下,这会比较方便。

    LD_LIBRARY_PATH

    要指示动态装载器首先检查某些目录,请将 LD_LIBRARY_PATH 变量设置成您希望搜索的目录。多个路径之间用冒号分隔;例如:

    $ export LD_LIBRARY_PATH="/usr/lib/old:/opt/lib"
    
    • 1

    导出 LD_LIBRARY_PATH 后,如有可能,所有从当前 shell 启动的可执行程序都将使用 /usr/lib/old 或 /opt/lib 中的库,如果仍不能满足一些共享库相关性要求,则转回到 /etc/ld.so.conf 中指定的库。

    14.profile与bashrc的区别

    1, 要搞清bashrc与profile的区别,首先要弄明白什么是交互式shell和非交互式shell,什么是login shell 和non-login shell。

    交互式模式就是shell等待你的输入,并且执行你提交的命令。这种模式被称作交互式是因为shell与用户进行交互。这种模式也是大多数用户非常熟悉的:登录、执行一些命令、签退。当你签退后,shell也终止了。
    shell也可以运行在另外一种模式:非交互式模式。在这种模式下,shell不与你进行交互,而是读取存放在文件中的命令,并且执行它们。当它读到文件的结尾,shell也就终止了。

    2, bashrc与profile都用于保存用户的环境信息,bashrc用于交互式non-loginshell,而profile用于交互式login shell。系统中存在许多bashrc和profile文件,下面逐一介绍:

    /etc/profile 此文件为系统的每个用户设置环境信息,当第一个用户登录时,该文件被执行. 并从/etc/profile.d目录的配置文件中搜集shell的设置.

    /etc/bashrc 为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取。有些linux版本中的/etc目录下已经没有了bashrc文件。

    ~/. profile每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!默认情况下,它设置一些环境变量,然后执行用户的.bashrc文件.
    ~/.bashrc 该文件包含专用于某个用户的bash shell的bash信息,当该用户登录时以及每次打开新的shell时,该文件被读取.

    另外,/etc/profile中设定的变量(全局)的可以作用于任何用户,而~/.bashrc等中设定的变量(局部)只能继承/etc/profile中的变量,他们是"父子"关系.

    15.环境变量配置的三个方法

    如想将一个路径加入到$PATH中,可以像下面这样做:

    1. 控制台中,不赞成使用这种方法,因为换个shell,你的设置就无效了,因此这种方法仅仅是临时使用,以后要使用的时候又要重新设置,比较麻烦。
      这个只针对特定的shell;
      $ PATH=“$PATH:/my_new_path” (关闭shell,会还原PATH)

    2. 修改/etc/profile文件,如果你的计算机仅仅作为开发使用时推荐使用这种方法,因为所有用户的shell都有权使用这些环境变量,可能会给系统带来安全性问题。 这里是针对所有的用户的,所有的shell;

    ​ $ vi /etc/profile

    ​ 在里面加入:
    export PATH=“$PATH:/my_new_path”

    1. 修改.bashrc文件,这种方法更为安全,它可以把使用这些环境变量的权限控制到用户级别,这里是针对某一个特定的用户,如果你需要给某个用户权限使用这些环境变量,你只需要修改其个人用户主目录下的.bashrc文件就可以了。

    ​ $ vi /root/.bashrc

    ​ 在里面加入:

    export PATH=“$PATH:/my_new_path”

    后两种方法一般需要重新注销系统才能生效,最后可以通过echo命令测试一下:

    ​ $ echo $PATH

    输出已经是新路径了。

    16.如何将目录添加到 PATH 环境变量

    本文讨论了一个重要的 Linux shell 环境变量,称为PATH,以及如何向该变量添加目录。

    通常,您在 Linux 系统上运行的大多数程序都可以完美运行,并且您的 shell 在执行时不会出现任何错误。但是,有时您可能希望从 Linux 系统上的非标准目录快速运行自定义脚本或程序。

    在这种情况下,您需要将它们的源目录添加到 shell 的 PATH 变量中,以便它知道在哪里可以找到它们。

    路径环境变量用于什么?

    环境变量控制 shell 的行为。如果您曾经 在 Linux 上使用过命令行,系统将依赖 PATH 变量来查找您正在输入的命令的位置。

    **PATH 是 Linux 中的一个内置环境变量,它告诉 shell 在哪些目录中搜索可执行文件以响应用户或应用程序发出的命令。**它包含以冒号分隔的目录列表,用于查找您输入的命令。

    如何在 Linux 中将目录添加到 PATH

    要立即查看 PATH 中的内容,请在终端中输入:

    linuxmi@linuxmi:~/www.linuxmi.com$ echo $PATH
    
    • 1

    在这里插入图片描述

    如您所见,此变量中的目录由冒号 ( :) 分隔。目前,shell 在以下目录中搜索二进制可执行文件:

    • /home/linuxmi/.nvm/versions/node/v17.4.0/bin
    • /home/linuxmi/.cargo/bin
    • /home/linuxmi/.local/bin
    • /usr/local/bin
    • /usr/local/sbin
    • /usr/local/bin
    • /usr/sbin
    • /usr/bin
    • /sbin
    • /bin
    • /usr/games
    • /usr/local/games
    • /snap/bin
    • /opt/mssql-tools/bin

    搜索 PATH 的顺序也很重要。因此,如果您有两个共享相同名称的可执行文件位于两个不同的目录中,则 shell 将运行位于 PATH 中第一个目录中的文件。

    临时将目录添加到 PATH

    现在假设您已经创建了一个位于/srv/scripts目录中的自定义 shell 脚本,并且您希望将此目录添加到您的 PATH 变量中。这是你应该做的:

    linuxmi@linuxmi:~/www.linuxmi.com$ export PATH=$PATH:/srv/scripts
    为了让事情更清楚,让我们分解一下这个语法。以下是每个参数的含义:
    
    • 1
    • 2
    • **export** 命令会将修改后的**PATH**变量导出到 shell 子进程环境。
    • 变量名之前的**$**意思是你指的是它的值。
    • **:/srv/scripts**部分指定**:**符号后面的内容应附加到当前 PATH 变量中包含的值。

    让我们看看 PATH 现在的样子:

    linuxmi@linuxmi:~/www.linuxmi.com$ echo $PATH
    
    
    • 1
    • 2

    在这里插入图片描述

    正如您在上面的输出中看到的,该 /srv/scripts 目录被添加到 PATH 变量的末尾。现在,您存储在 /srv/scripts 目录中的文件可以在任何地方执行,而无需指定它们的完整路径。

    此外,如果您认为您的目录应该在其他所有内容之前被搜索,您可以将其添加到$PATH

    export PATH=/srv/scripts:$PATH
    
    • 1

    请务必注意,如果您退出终端或从系统注销,PATH 将恢复并且更改将丢失,因为这种设置 PATH 的方法仅在当前终端会话期间临时记住更改。

    要使其永久化,请查看以下部分。

    将目录永久添加到 PATH

    要使更改永久生效,您需要在 shell 配置文件中定义 PATH 变量。

    默认系统范围的 PATH 值在 /etc/profile 文件中指定。将目录添加到单个用户的路径的最佳位置是修改该用户的.bashrc文件。

    使用文本编辑器打开文件,滚动到文件底部,并在文件末尾添加以下行:

    linuxmi@linuxmi:~/www.linuxmi.com$ vim ~/.bashrc
    
    • 1

    ~/.bashrc

    export PATH=$PATH:/srv/scripts
    
    • 1

    在这里插入图片描述

    source最后,保存文件并使用以下命令将新的 PATH 加载到当前 shell 会话中:

    linuxmi@linuxmi:~/www.linuxmi.com$ source ~/.bashrc
    
    • 1

    要确认目录已成功添加,请键入以下 echo 命令检查路径:

    linuxmi@linuxmi:~/www.linuxmi.com$ echo $PATH
    
    • 1

    在这里插入图片描述

    结论

    在Linux中向您的用户或全局 PATH 变量添加新目录非常简单。在本文中,我们了解到有两种方法可以做到这一点:暂时的和永久的。

    17.Makefile常用基础知识梳理

    17.1.什么是make、Makefile?

    make是一个 构建工具,主要用于C/C++项目。

    Makefile是一个编译脚本,使用 make 工具解释Makefile(makefile)文件中的指令(编译指令)进行我们的项目编译。

    在Linux环境下进行开发,工程源文件较少时,直接使用gcc进行编译。源文件较多及工程复杂时,就可以使用 Makefile(makefile) 来对我们的工程进行管理,然后使用 make 工具解释Makefile(makefile)文件中的指令(编译指令)进行我们的项目编译。即借助Makefile可以做到 自动化编译

    17.2.Makefile的实例

    1、基础实例

    万年不变helloworld,使用make编译hello.c。对应的Makefile文件:

    hello:hello.c
     gcc hello.c -o hello
    
    • 1
    • 2

    2、开源项目实例

    之前我们在实践分享 | 基于framebuffer的lvgl的移植使用中也是使用Makefile来管理工程:

    #
    # Makefile
    #
    CC ?= gcc
    LVGL_DIR_NAME ?= lvgl
    LVGL_DIR ?= ${shell pwd}
    CFLAGS ?= -O3 -g0 -I$(LVGL_DIR)/ -Wall -Wshadow -Wundef -Wmaybe-uninitialized -Wmissing-prototypes -Wno-discarded-qualifiers -Wall -Wextra -Wno-unused-function -Wundef -Wno-error=strict-prototypes -Wpointer-arith -fno-strict-aliasing -Wno-error=cpp -Wuninitialized -Wmaybe-uninitialized -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits -Wsizeof-pointer-memaccess -Wno-format-nonliteral -Wno-cast-qual -Wunreachable-code -Wno-switch-default -Wno-switch-enum -Wreturn-type -Wmultichar -Wformat-security -Wno-ignored-qualifiers -Wno-error=pedantic -Wno-sign-compare -Wno-error=missing-prototypes -Wdouble-promotion -Wclobbered -Wdeprecated -Wempty-body -Wtype-limits -Wshift-negative-value -Wstack-usage=1024 -Wno-unused-value -Wno-unused-parameter -Wno-missing-field-initializers -Wuninitialized -Wmaybe-uninitialized -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits -Wsizeof-pointer-memaccess -Wno-format-nonliteral -Wpointer-arith -Wno-cast-qual -Wmissing-prototypes -Wunreachable-code -Wno-switch-default -Wswitch-enum -Wreturn-type -Wmultichar -Wno-discarded-qualifiers -Wformat-security -Wno-ignored-qualifiers -Wno-sign-compare
    LDFLAGS ?= -lm
    BIN = demo
    
    
    #Collect the files to compile
    MAINSRC = ./main.c
    
    include $(LVGL_DIR)/lvgl/lvgl.mk
    include $(LVGL_DIR)/lv_drivers/lv_drivers.mk
    include $(LVGL_DIR)/lv_examples/lv_examples.mk
    
    OBJEXT ?= .o
    
    AOBJS = $(ASRCS:.S=$(OBJEXT))
    COBJS = $(CSRCS:.c=$(OBJEXT))
    
    MAINOBJ = $(MAINSRC:.c=$(OBJEXT))
    
    SRCS = $(ASRCS) $(CSRCS) $(MAINSRC)
    OBJS = $(AOBJS) $(COBJS)
    
    ## MAINOBJ -> OBJFILES
    
    all: default
    
    %.o: %.c
     @$(CC)  $(CFLAGS) -c $< -o $@
     @echo "CC $<"
        
    default: $(AOBJS) $(COBJS) $(MAINOBJ)
     $(CC) -o $(BIN) $(MAINOBJ) $(AOBJS) $(COBJS) $(LDFLAGS)
    
    clean: 
     rm -f $(BIN) $(AOBJS) $(COBJS) $(MAINOBJ)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    执行一个make命令很方便地就生成了可执行程序demo。

    17.3.Makefile的常用知识

    前面分享了两个Makefile实例,大概知道Makefile里面都有些什么内容了。我们学习编程语言的时候,最开始都是先学习一些基本的概念及语法,同样的,对于Makefile,我们想要看懂Makefile文件或者要自己能修改、写出Makefile文件,自然也要学习一些Makefile的基本语法。

    Makefile的内容很多,这里我们只专注于一些常用的Makefile知识点,基础知识掌握得牢固之后,其它一些冷门的、高级的用法我们在实践中遇到再去查资料学习也不迟。

    就比如我们学习编程语言,有了基础之后,就可以去写代码实践了,而不是把厚厚的一本参考书的所有知识点都啃完之后采取动手,一下子输入太多知识而没有及时去实践巩固,就很容易忘记。

    本文分享Makefile的如下几点常用知识:

    在这里插入图片描述

    17.4.1、基本规则

    Makefile文件中最重要的是规则。基本规则的格式如下:

    # 基本规则
    target:prerequisites 
        command
    
    • 1
    • 2
    • 3

    其中,target 为目标,prerequisites 为依赖。command 为make需要执行的命令。

    Makefile文件使用#进行注释。

    • 目标:往往是程序的中间或者最终生成的文件名,比如目标文件、可执行文件等。
    • 依赖:是指用来产生目标文件的输入文件名,一个目标往往依赖于一个或多个文件。
    • 命令:是指任何一个文件发生改动之后,需要重新生成目标文件需要执行的命令,这里可以有多条命令,但是每个命令必须单独占一行,且需要注意的是,每个命令的前面必须有一个 ,因为make是用过来识别命令行的,进而完成相应的动作。

    对照着我们上面举例的两个Makefile文件,看看是不是都遵循着这样的规则。

    17.4.2、变量

    Makefile中的变量与编程语言中的变量的概念有点不一样,跟C语言的宏倒是有些类似,用于记录一些信息,在Makefile被解析执行时,变量调用的地方就可以使用这些信息。特别是多处要用到同样的信息、用到较长的信息时,定义变量就很有优势。

    (1)定义变量

    定义变量有赋值的过程,Makefile提供了四种赋值方式,如:

    # 直接给变量赋值。
    VAR = xxx
    # 是在该变量没有被赋值的情况下为其赋值。
    VAR ?= xxx
    # 将":="右边中包含的变量直接展开给左边的变量赋值。
    VAR := xxx
    # 追加赋值,可以往变量后面增加新的内容。
    VAR += xxx
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    定义及使用变量:

    # 定义变量
    VAR = xxx
    # 使用变量
    $(VAR)
    
    • 1
    • 2
    • 3
    • 4

    对于以上四种赋值方式,可以看个网上易懂的例子:

    例子:
    a = 1
    b = 2
    c := $(a) 3
    d = 4
    d ?= 5
    b += 6
    结果:
    a=1
    c=1 3
    d=4
    b=2 6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    (2)内置变量

    Make命令提供一系列内置变量,常用的有:

    变量名说明
    CURDIR当前路径
    CCC语言编译器的名称
    CPPC语言预处理器的名称
    CXXC++语言的编译器名称
    RM删除文件程序的名称
    CFLAGSC语言编译器的编译选项,无默认值
    CPPFLAGSC语言预处理器的编译选项,无默认值
    CXXFLAGSC++语言编译器的编译选项,无默认值

    更多内置变量,可查阅:

    https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html

    17.4.3、分支判断

    Makefile中,分支判断有如下几种情况:

    • ifeq:判断参数是否不相等,相等为 true,不相等为 false。
    • ifneq:判断参数是否不相等,不相等为 true,相等为 false。
    • ifdef:判断是否有值,有值为 true,没有值为 false。
    • ifndef:判断是否有值,没有值为 true,有值为 false。

    使用方法类似C语言中的if用法。格式如:

    ifeq (ARG1, ARG2)
    ...
    else
    ...
    endif
    
    • 1
    • 2
    • 3
    • 4
    • 5

    例如根据不同的CPU架构选择不同的编译器:

    ARCH ?= x86
    ifeq ($(ARCH),x86)
     CC = gcc
    else
     CC = arm-linux-gnueabihf-gcc
    endif
    
    hello:hello.c
     $(CC) hello.c -o hello
     @echo $(CC)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在echo前面加上echo可以关闭回显。

    上面这一段Makefile中的变量arch的值默认为x86,我们也可以在执行make命令时指定变量的值,就可以在不修改Makefile文件的情况下灵活地切换编译器,如:

    make ARCH=x86
    make ARCH=arm
    
    • 1
    • 2

    17.4.4、头文件依赖

    有些工程用,各模块有自己的一个Makefile文件,提供给工程总的Makefile文件使用。总的Makfile文件可以使用关键字 include 包含其它Makefile文件,格式如:

    include 
    
    • 1

    例如上面实例中的:

    include $(LVGL_DIR)/lvgl/lvgl.mk
    include $(LVGL_DIR)/lv_drivers/lv_drivers.mk
    include $(LVGL_DIR)/lv_examples/lv_examples.mk
    
    • 1
    • 2
    • 3

    17.4.5、显示规则与隐式规则

    (1)显示规则

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

    hello:hello.o
        gcc hello.o -o hello
    
    • 1
    • 2

    这就是一条显示规则。工程较简单时,常常使用显示规则来编写Makefile。但工程的结构比较复杂时,Makefile文件中常常会穿插着一些隐式规则来简化Makefile。下面看看是什么时隐式规则。

    (2)隐式规则

    隐式规则 。隐含规则是系统或用户预先定义好的一些特殊规则,主要是一些常用的依赖关系和更新命令。隐含规则中出现的目标文件和依赖文件都只使用文件的扩展名。

    如果Makefile 文件里面没有显式给出文件的依赖关系的时候,make 就会根据文件的扩展名找到相应的隐含规则,然后按照隐含规则来更新目标。隐式规则的例子如:

    hello:hello.o
        $(CC) $^ -o $@
    
    • 1
    • 2

    其中,$@ 代表了目标文件 ,$< 代表了所有依赖文件。其中, $@$< 也称作系统的 自动化变量 。类似的还有如下几个常用的自动化变量:

    • @ 类似,但 $% 仅匹配“库”类型的目标文件。
    • $<:依赖中的第一个目标文件。
    • $+:所有的依赖目标,即使依赖中有重复的也原样保留。
    • $?:所有比目标要新的依赖目标。

    17.4.6、实目标与伪目标

    Makefile 文件中的目标分为两类:实目标和伪目标。

    (1)实目标

    实目标是真正要生成的以文件形式存放在磁盘上的目标。如:

    hello:hello.o
        $(CC) $^ -o $@ 
    
    • 1
    • 2

    其中,hello 文件就是实目标。

    (2)伪目标

    伪目标不要求生成实际的文件,它主要是用于完成一些辅助操作。如:

    clean:
        rm -rf hello.o hello
    
    • 1
    • 2

    其中的 clean 就是一个伪目标。我们在命令里面输入命令:make clean 就可以执行删除操作:

    rm -rf hello.o hello
    
    • 1

    但是这种书写形式不是很严谨,因为可能在当前目录下面存在文件名为 clean 的文件,因为这时候, 后面没有依赖文件,所以make 就认为这个文件是最新的,所以就不会执行 rm -rf hello.o hello 。所以为了避免这种情况的发生,Makefile使用**.PHONY** 来区分伪目标,使用如:

    .PHONY:clean
    clean:
        rm -rf hello.o hello
    
    • 1
    • 2
    • 3

    使用 .PHONY 说明clean是一个伪目标。

    类似 .PHONY 这样的特殊的内置目标名称还有很多,可查阅:

    https://www.gnu.org/software/make/manual/html_node/Special-Targets.html#Special-Targets

    17.4.7、函数

    Makefile中有很多使用的内置函数,借助这些函数可以使我们的Makefile更为简洁。Makefile函数的调用方式与使用变量的方式类似,如:

    $(函数名 参数)
    
    • 1

    或者:

    ${函数名 参数}
    
    • 1

    下面介绍一些常用的函数:

    (1)wildcard函数

    wildcard函数用于获取文件列表,并使用空格分隔开。语法如:

    $(wildcard 匹配规则)
    
    • 1

    例如我们有如下工程:

    在这里插入图片描述

    我们可以使用wildcard函数获取src文件夹下的文件:

    SRC_FILES = $(wildcard src/*.c)
    
    target1:
     @echo $(SRC_FILES)
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    (2)patsubst函数

    patsubst函数功能为模式字符串替换。语法如:

    $(patsubst 匹配规则, 替换规则, 输入的字符串)
    
    • 1

    patsubst函数看起来貌似有点复杂,不做过多解释,看个例子就知道什么意思了。还是使用上面的例子:

    OBJ_FILES = $(patsubst %.c, %.o, hello.c)
    
    target1:
     @echo $(OBJ_FILES)
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    我们输入的hello.c符合匹配规则**%.c,所以按照替换规则%.o**进行替换得到hello.o。

    %是一个通配符,用于匹配任意个字符。

    (3)subst函数

    subst函数功能为字符串替换。语法如:

    $(subst 源字符串, 目标字符串, 输入的字符串)
    
    • 1

    例子:

    INPUT_STR = hello world
    OUTPUT_STR = $(subst hello, HELLO, $(INPUT_STR))
    
    target1:
     @echo $(OUTPUT_STR)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    subst函数把输入字符串 hello world 中的源字符串hello替换成目标字符串HELLO。

    (4)notdir函数

    notdir函数用于去除文件路径中的目录部分。语法如:

    $(notdir 文件名)
    
    • 1

    使用上面的例子来演示:

    SRC_FILES = $(notdir src/src1.c)
    
    target1:
     @echo $(SRC_FILES)
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    notdir函数去掉了src/src1.c前面的路径src/,输出src1.c。这里只是输出了一个文件,我们可以与上面的wildcard函数结合使用输出多个文件,如:

    SRC = $(wildcard src/*.c)
    SRC_FILES = $(notdir $(SRC))
    
    target1:
     @echo $(SRC_FILES)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    先是使用wildcard函数获取带有路径的文件列表,再使用notdir函数去掉前面的路径。

    更多内置函数可查阅:

    https://www.gnu.org/software/make/manual/html_node/Functions.html

    17.4.5练习

    根据我们上面学习的知识,我们可以来练习给如下工程编写一个Makefile文件:

    在这里插入图片描述

    Makefile:

    # 默认架构
    ARCH ?= x86
    
    # 源文件目录
    SRCS_DIR = src
    
    # 头文件目录
    INCS_DIR = inc
    
    # 编译输出目录
    BUILD_DIR = build
    
    # 源文件
    SRCS = $(wildcard $(SRCS_DIR)/*.c)
    
    # 头文件
    INCS = $(wildcard $(INCS_DIR)/*.h)
    
    # 目标文件
    OBJS = $(patsubst %.c, $(BUILD_DIR)/%.o, $(notdir $(SRCS)))
    
    # C编译选项,显示警告
    CFLAGS = -Wall
    
    # 目标APP
    TARGET_APP = hello_makefile
    
    # 根据ARCH选择编译器
    ifeq ($(ARCH), x86)
     CC = gcc
    else
     CC = arm-linux-gnueabihf-gcc
    endif
    
    # 链接
    $(BUILD_DIR)/$(TARGET_APP):$(OBJS) 
     $(CC) -o $@ $^ -I$(INCS_DIR) $(CFLAGS)
    
    # 编译
    $(BUILD_DIR)/%.o:$(SRCS_DIR)/%.c $(INCS)
     @mkdir -p $(BUILD_DIR)
     $(CC) -c -o $@ $< -I$(INCS_DIR) $(CFLAGS)
    
    # 清除
    .PHTHY:clean
    clean:
     rm -rf $(BUILD_DIR)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    以上就是本次的分享,学习了这些Makefile基础知识之后,我们就可以看懂很多工程的Makefile文件。

    18.三个Makefile通用模板分享

    18.0.编译可执行文件Makefile

    VERSION  =1.00
    CC   =gcc
    DEBUG   =-DUSE_DEBUG
    CFLAGS  =-Wall
    SOURCES   =$(wildcard ./source/*.c)
    INCLUDES   =-I./include
    LIB_NAMES  =-lfun_a -lfun_so
    LIB_PATH  =-L./lib
    OBJ   =$(patsubst %.c, %.o, $(SOURCES))
    TARGET  =app
    
    #links
    $(TARGET):$(OBJ)
     @mkdir -p output
     $(CC) $(OBJ) $(LIB_PATH) $(LIB_NAMES) -o output/$(TARGET)$(VERSION)
     @rm -rf $(OBJ)
     
    #compile
    %.o: %.c
     $(CC) $(INCLUDES) $(DEBUG) -c $(CFLAGS) $< -o $@
    
    .PHONY:clean
    clean:
     @echo "Remove linked and compiled files......"
     rm -rf $(OBJ) $(TARGET) output 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    【要点说明】

    【1】程序版本

    开发调试过程可能产生多个程序版本,可以在目标文件后(前)增加版本号标识。

    VERSION = 1.00
    $(CC) $(OBJ) $(LIB_PATH) $(LIB_NAMES) -o output/$(TARGET)$(VERSION)
    
    • 1
    • 2

    【2】编译器选择

    Linux下为gcc/g++;arm下为arm-linux-gcc;不同CPU厂商提供的定制交叉编译器名称可能不同,如Hisilicon“arm-hisiv300-linux-gcc”。

    CC = gcc
    
    • 1

    【3】宏定义

    开发过程,特殊代码一般增加宏条件来选择是否编译,如调试打印输出代码。-D是标识,后面接着的是“宏”。

    DEBUG =-DUSE_DEBUG
    
    • 1

    【4】编译选项

    可以指定编译条件,如显示警告(-Wall),优化等级(-O)。

    CFLAGS =-Wall -O
    
    • 1

    【5】源文件

    指定源文件目的路径,利用“wildcard”获取路径下所有依赖源文件。

    SOURCES =$(wildcard ./source/*.c)
    
    • 1

    【6】头文件

    包含依赖的头文件,包括源码文件和库文件的头文件。

    INCLUDES =-I./include
    
    • 1

    【7】库文件名称

    指定库文件名称,库文件有固定格式,静态库为libxxx.a;动态库为libxxx.so,指定库文件名称只需写“xxx”部分,

    LIB_NAMES =-lfun_a -lfun_so
    
    • 1

    【8】库文件路径

    指定依赖库文件的存放路径。注意如果引用的是动态库,动态库也许拷贝到“/lib”或者“/usr/lib”目录下,执行应用程序时,系统默认在该文件下索引动态库。

    LIB_PATH =-L./lib
    
    • 1

    【9】目标文件

    调用“patsubst”将源文件(.c)编译为目标文件(.o)。

    OBJ =$(patsubst %.c, %.o, $(SOURCES))
    
    • 1

    【10】执行文件

    执行文件名称

    TARGET =app
    
    • 1

    【11】编译

    %.o: %.c
     $(CC) $(INCLUDES) $(DEBUG) $(CFLAGS) $< -o $@
    
    • 1
    • 2

    【12】链接

    可创建一个“output”文件夹存放目标执行文件。链接完输出目标执行文件,可以删除编译产生的临时文件(.o)。

    $(TARGET):$(OBJ)
     @mkdir -p output
     $(CC) $(OBJ) $(LIB_PATH) $(LIB_NAMES) -o output/$(TARGET).$(VERSION)
     @rm -rf $(OBJ)
    
    • 1
    • 2
    • 3
    • 4

    【13】清除编译信息

    执行“make clean”清除编译产生的临时文件。

    .PHONY:clean
    clean:
     @echo "Remove linked and compiled files......"
     rm -rf $(OBJ) $(TARGET) output 
    
    • 1
    • 2
    • 3
    • 4

    18.1.编译静态库Makefile

    VERSION     =
    CC          =gcc
    DEBUG   =
    CFLAGS  =-Wall
    AR   =ar
    ARFLAGS     =rv
    SOURCES   =$(wildcard *.c)
    INCLUDES    =-I.
    LIB_NAMES   =
    LIB_PATH  =
    OBJ         =$(patsubst %.c, %.o, $(SOURCES))
    TARGET      =libfun_a
    
    #link
    $(TARGET):$(OBJ)
     @mkdir -p output
     $(AR) $(ARFLAGS) output/$(TARGET)$(VERSION).a $(OBJ)
     @rm -rf $(OBJ)
    
    #compile
    %.o: %.c
     $(CC) $(INCLUDES) $(DEBUG) -c $(CFLAGS) $< -o $@
      
    .PHONY:clean
    clean:
     @echo "Remove linked and compiled files......"
     rm -rf $(OBJ) $(TARGET) output 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    【要点说明】

    基本格式与“编译可执行Makefile”一致,不同点包括以下。

    【1】使用到“ar”命令将目标文件(.o)链接成静态库文件(.a)。静态库文件固定命名格式为:libxxx.a。

    18.2.编译动态库Makefile

    VERSION   =
    CC        =gcc
    DEBUG     =
    CFLAGS    =-fPIC -shared 
    LFLAGS   =-fPIC -shared 
    SOURCES   =$(wildcard *.c)
    INCLUDES  =-I.
    LIB_NAMES =
    LIB_PATH  =
    OBJ       =$(patsubst %.c, %.o, $(SOURCES))
    TARGET    =libfun_so
    
    #link
    $(TARGET):$(OBJ)
     @mkdir -p output
     $(CC) $(OBJ) $(LIB_PATH) $(LIB_NAMES) $(LFLAGS) -o output/$(TARGET)$(VERSION).so
     @rm -rf $(OBJ)
     
    #compile
    %.o: %.c
     $(CC) $(INCLUDES) $(DEBUG) -c $(CFLAGS) $< -o $@
    
    .PHONY:clean
    clean:
     @echo "Remove linked and compiled files......"
     rm -rf $(OBJ) $(TARGET) output 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    【要点说明】

    基本格式与“编译可执行Makefile”一致,不同点包括以下。

    【1】编译选项和链接选项增加“-fPIC -shared ”选项。动态库文件固定命名格式为libxxx.so。

    18.4.编译应用程序

    编写测试例程,文件存放目录结构如下,头文件存放在“include”目录,库文件存放在“lib”目录,源文件存放在“source”目录,Makefile在当前目录下。

    在这里插入图片描述

    源码1:

    /*头文件*/
    #ifndef _FUN0_H_
    #define _FUN0_H_
    #endif
    
    extern void fun0_printf(void);
    extern void fun1_printf(void);
    
    /*源文件*/
    #include 
    #include "fun0.h"
    
    void fun0_printf(void)
    {
        printf("Call \'fun0\'. \r\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    源码2:

    /*头文件*/
    #ifndef _FUN1_H_
    #define _FUN1_H_
    #endif
    
    extern void fun1_printf(void);
    
    /*源文件*/
    #include 
    #include "fun1.h"
    
    void fun1_printf(void)
    {
        printf("Call \'fun1\'.\r\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    主函数源码:

    /*源文件*/
    #include 
    #include "fun0.h"
    #include "fun1.h"
    #include "fun_lib_a.h"
    #include "fun_lib_so.h"
    
    int main(void)
    {
        #ifdef USE_DEBUG
            printf("Debug Application startup.\r\n");
        #endif
        
            fun0_printf();
            fun1_printf();
            fun_lib_a_printf();
            fun_lib_so_printf();
            return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    库文件,“./lib”目录下存放两个库文件,一个静态库libfun_a.a,一个动态库libfun_so.so。

    Makefile文件即为“2.1节”的Makefile模板。

    测试运行:

    在这里插入图片描述

    【如果执行文件提示无“libfun_so.so”,则需拷贝“libfun_so.so”到根目录下的“/lib”或者“/usr/lib”目录下,因为系统执行程序,默认从该路径引脚动态库】

    18.4.1生成静态库

    编写测试例程,生产的库文件即为“3.1节”调用的库文件(libfun_a.a)。文件存放目录结构如下:

    在这里插入图片描述

    源文件:

    /*头文件*/
    #ifndef _FUN_LIB_A_H_
    #define _FUN_LIB_A_H_
    #endif
    
    extern void fun_lib_a_printf(void);
    
    /*源文件*/
    #include 
    #include "fun_lib_a.h"
    
    void fun_lib_a_printf(void)
    {
        printf("Call \'fun_lib_a\'.\r\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    Makefile文件即为“2.2节”的Makefile模板。

    编译生成静态库:

    在这里插入图片描述

    18.4.2生成动态库

    编写测试例程,生产的库文件即为“3.1节”调用的库文件(libfun_so.so)。文件存放目录结构如下:

    在这里插入图片描述

    源文件:

    /*头文件*/
    #ifndef _FUN_LIB_SO_H_
    #define _FUN_LIB_SO_H_
    #endif
    
    extern void fun_lib_so_printf(void);
    
    /*头文件*/
    
    #include 
    #include "fun_lib_so.h"
    
    void fun_lib_so_printf(void)
    {
        printf("Call \'fun_lib_so\'.\r\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    编译生成动态库:
    在这里插入图片描述

    参考博文

    1.几个Makefile通用模板分享

    2.Makefile常用基础知识梳理

  • 相关阅读:
    10 - Spring AOP介绍与使用
    【Kotlin】从字节码角度理解kotlin构造函数、成员变量、init代码块执行顺序
    SAP-MM-查找采购订单的创建和修改日期
    21-关键帧动画
    如何批量为文件夹命名
    文件操作--I/O
    C语言面试题 - 指针声明
    makefile通用模板
    计算机毕业设计Java滁州市的围棋协会网站(源码+系统+mysql数据库+lw文档)
    解决微信小程序报错:“SyntaxError:Unexpected end of JSON input”
  • 原文地址:https://blog.csdn.net/weixin_43297891/article/details/126921686