既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
[root@bogon demo]# gcc -static main.o libmymath.a
[root@bogon demo]# ls
add.c a.out div.o main.c sub.c test.h
add.o div.c libmymath.a main.o sub.o
其中,-static 选项强制 GCC 编译器使用静态链接库。
注意,如果 GCC 编译器提示无法找到 libmymath.a,还可以使用如下方式完成链接操作:
[root@bogon demo]# gcc main.o -static -L /root/demo/ -lmymath
[root@bogon demo]# ls
add.c a.out div.o main.c sub.c test.h
add.o div.c libmymath.a main.o sub.o
其中,-L(大写的 L)选项用于向 GCC 编译器指明静态链接库的存储位置(可以借助 pwd 指令查看具体的存储位置); -l(小写的 L)选项用于指明所需静态链接库的名称,注意这里的名称指的是 xxx 部分,且建议将 -l 和 xxx 直接连用(即 -lxxx),中间不需有空格。
由此,就生成了 a.out 可执行文件:
[root@bogon demo]# ./a.out
Input two numbers: 10 2
10+2=12
10-2=8
10÷2=5
2. 用GCC制作动态链接库
前面章节中,给读者详细阐述了什么是库文件、什么是静态链接库和动态链接库,同时还介绍了手动创建静态链接库的过程。在此基础上,本节继续讲解动态链接库的创建和使用。
有关动态链接库,以及它的特性、和静态链接库的区别,读者可阅读《GCC使用静态链接库和动态链接库》一节做详细了解,这里不再做赘述。
为了方便读者更好地理解创建和使用动态链接库的过程,本节仍以在《静态链接库的创建和使用》一节中创建好的 demo 项目(一个 C 语言多文件项目)为例,如下是该目录的具体构成和相关源码:
[root@bogon demo]# ls <- demo 目录结构
add.c div.c main.c sub.c test.h
[root@bogon demo]# cat test.h <- test.h 文件内容
#ifndef __TEST_H_
#define __TEST_H_
int add(int a,int b);
int sub(int a,int b);
int div(int a,int b);
#endif
[root@bogon demo]# cat add.c <- add.c 文件内容
#include “test.h”
int add(int a,int b)
{
return a + b;
}
[root@bogon demo]# cat sub.c <- sub.c 文件内容
#include “test.h”
int sub(int a,int b)
{
return a - b;
}
[root@bogon demo]# cat div.c <- div.c 文件内容
#include “test.h”
int div(int a,int b)
{
return a / b;
}
[root@bogon demo]# cat main.c <- main.c 文件内容
#include <stdio.h>
#include "test.h" //必须引入头文件
int main(void)
{
int m, n;
printf("Input two numbers: ");
scanf("%d %d", &m, &n);
printf("%d+%d=%d\n", m, n, add(m, n));
printf("%d-%d=%d\n", m, n, sub(m, n));
printf("%d÷%d=%d\n", m, n, div(m, n));
return 0;
}
动态链接库的创建
总的来说,动态链接库的创建方式有 2 种。
- 直接使用源文件创建动态链接库,采用 gcc 命令实现的基本格式如下:
gcc -fpic -shared 源文件名… -o 动态链接库名
其中,-shared 选项用于生成动态链接库;-fpic(还可写成 -fPIC)选项的功能是,令 GCC 编译器生成动态链接库(多个目标文件的压缩包)时,表示各目标文件中函数、类等功能模块的地址使用相对地址,而非绝对地址。这样,无论将来链接库被加载到内存的什么位置,都可以正常使用。
例如,由 demo 项目中的 add.c、sub.c 和 div.c 这 3 个源文件生成一个动态链接库,执行命令为:
[root@bogon demo]# ls
add.c div.c main.c sub.c test.h
[root@bogon demo]# gcc -fpic -shared add.c sub.c div.c -o libmymath.so
[root@bogon demo]# ls
add.c div.c libmymath.so main.c sub.c test.h
注意,动态链接库的命令规则和静态链接库完全相同,只不过在 Linux 发行版系统中,其后缀名用 .so 表示;Windows 系统中,后缀名为 .dll。
- 先使用 gcc -c 指令将指定源文件编译为目标文件。仍以 demo 项目中的 add.c、sub.c 和 div.c 为例,先执行如下命令:
[root@bogon demo]# ls
add.c div.c main.c sub.c test.h
[root@bogon demo]# gcc -c -fpic add.c sub.c div.c
[root@bogon demo]# ls
add.c add.o div.c div.o main.c sub.c sub.o test.h
注意,为了后续生成动态链接库并能正常使用,将源文件编译为目标文件时,也需要使用 -fpic 选项。
在此基础上,接下来利用上一步生成的目标文件,生成动态链接库:
[root@bogon demo]# gcc -shared add.o sub.o div.o -o libmymath.so
[root@bogon demo]# ls
add.c add.o div.c div.o libmymath.so main.c sub.c sub.o test.h
以上 2 种操作,生成的动态链接库是完全一样的,读者任选一种即可。
动态链接库的使用
通过前面章节的学习我们知道,动态链接库的使用场景就是和项目中其它源文件或目标文件一起参与链接。以 demo 项目为例,前面我们将 add.c、sub.c 和 div.c 打包到了 libmymath.so 动态链接库中,此时该项目中仅剩 main.c 源程序文件,因此执行 demo 项目也就演变成了将 main.c 和 libmymath.so 进行链接,进而生成可执行文件。
注意,test.h 头文件并不直接参与编译,因为在程序的预处理阶段,已经对项目中需要用到的头文件做了处理。
执行如下指令,即可借助动态链接库成功生成可执行文件:
[root@bogon demo]# gcc main.c libmymath.so -o main.exe
[root@bogon demo]# ls
add.c div.c libmymath.so main.c main.exe sub.c test.h
注意,生成的 main.exe 通常无法直接执行,例如:
[root@bogon demo]# ./main.exe
./a.out: error while loading shared libraries: libd.so: cannot open shared object file: No such file or directory
可以看到,执行过程中无法找到 libmymath.so 动态链接库。通过执行ldd main.exe
指令,可以查看当前文件在执行时需要用到的所有动态链接库,以及各个库文件的存储位置:
[root@bogon demo]# ldd main.exe
linux-vdso.so.1 => (0x00007fff423ff000)
libmymath.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00000037e2c00000)
/lib64/ld-linux-x86-64.so.2 (0x00000037e2800000)
可以看到,main.exe 文件的执行需要 4 个动态链接库的支持,其中就包括 libmymath.so,但该文件无法找到,因此 main.exe 执行会失败。
运行由动态链接库生成的可执行文件时,必须确保程序在运行时可以找到这个动态链接库。常用的解决方案有如下几种:
- 将链接库文件移动到标准库目录下(例如 /usr/lib、/usr/lib64、/lib、/lib64);
- 在终端输入
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx
,其中 xxx 为动态链接库文件的绝对存储路径(此方式仅在当前终端有效,关闭终端后无效); - 修改~/.bashrc 或~/.bash_profile 文件,即在文件最后一行添加
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx
(xxx 为动态库文件的绝对存储路径)。保存之后,执行source .bashrc
指令(此方式仅对当前登陆用户有效)。
本操作系统(CentOS 6.5 64 位)中,只需要将 libmymath.so 库文件移动 /usr/lib64 或者 /lib64 目录下,即可使 main.exe 成功执行:
[root@bogon demo]# ldd main.exe
linux-vdso.so.1 => (0x00007fff06fb3000)
libmymath.so => /lib64/libmymath.so (0x00007f65b2a62000)
libc.so.6 => /lib64/libc.so.6 (0x00000037e2c00000)
/lib64/ld-linux-x86-64.so.2 (0x00000037e2800000)
[root@bogon demo]# ./main.exe
Input two numbers: 10 2
10+2=12
10-2=8
10÷2=5
3. GCC找不到库文件怎么办?
我们已经了解了如何创建静态链接库和动态链接库,并学会了如何使用它们。但即便是相同的操作,由于所用操作系统的环境不同,很多读者在实操中会碰到各种各样的问题,其中 GCC 编译器提示“找不到库文件(No such file or directory)”,就是最常见的问题之一。
关于 GCC 提示找不到指定库文件的这个问题,通常出现在以下 2 个场景中:
- 利用静态库或者动态库文件实现链接操作(生成可执行文件)时,GCC 可能会提示“xxx:No such file or directory”(其中 xxx 表示查找失败的静态库或者动态库);
- 执行借助动态库生成的可执行文件时,GCC 可能会提示“./main.exe: error while loading shared libraries: xxx.so: cannot open shared object file: No such file or directory”(其中 xxx 表示动态库的文件名)。
本节将就以上这 2 种情况,给读者分析 GCC 编译器查找库文件失败的原因,同时会给出相应的解决方案。
GCC生成可执行文件时找不到库文件
要想彻底解决这个问题,读者就必须先了解在生成可执行文件时,GCC 编译器默认的查找库文件的路径。
通过前面的学习我们知道,程序链接阶段指明所用库文件的方式有 2 种。假设当前 mian.c 文件需要借助 libmymath.a 才能完成链接,则完成链接操作的 gcc 指令有以下 2 种写法:
[root@bogon demo]# gcc -static main.c libmymath.a -o main.exe
[root@bogon demo]# gcc -static main.c -lmymath -o main.exe
当以第一种写法完成链接操作时,GCC 编译器只会在当前目录中(这里为 demo 目录)查找 libmymath.a 静态链接库;反之,如果使用 -l(小写的 L)选项指明了要查找的静态库的文件名,则 GCC 编译器会按照如下顺序,依次到指定目录中查找所需库文件:
- 如果 gcc 指令使用 -L 选项指定了查找路径,则 GCC 编译器会优先选择去该路径下查找所需要的库文件;
- 再到 Linux 系统中 LIBRARY_PATH 环境变量指定的路径中搜索需要的库文件;
- 最后到 GCC 编译器默认的搜索路径(比如 /lib、/lib64、/usr/lib、/usr/lib64、/usr/local/lib、/usr/local/lib64 等,不同系统环境略有差异)中查找。
如果读者使用第一种方法完成链接操作,但 GCC 编译器提示找不到所需库文件,表明所用库文件并未存储在当前路径下,解决方案就是手动找到库文件并将其移至当前路径,然后重新执行链接操作。
反之,如果读者使用的是第二种方法,也遇到了 GCC 编译器提示未找到所需库文件,表明库文件的存储路径不对,解决方案有以下 3 种:
- 手动找到该库文件,并在 gcc 指令中用 -L 选项明确指明其存储路径。比如 libmymath.a 静态库文件存储在 /usr 目录下,则完成链接操作的 gcc 指令应为
gcc -static main.c -L/usr -lmymath -o main.exe
; - 将库文件的存储路径添加到 LIBRARY_PATH 环境变量中。仍以库文件存储在 /usr 目录下,则通过执行
export LIBRARY_PATH=$LIBRARY_PATH:/usr
指令,即可将 /usr 目录添加到该环境变量中(此方式仅在当前命令行窗口中有效); - 将库文件移动到 GCC 编译器默认的搜索路径中。
GCC运行可执行文件时找不到动态库文件
执行已生成的可执行文件时,如果 GCC 编译器提示找不到所需的库文件,这意味着 GCC 编译器无法找到支持可执行文件运行的某些动态库文件。
事实上,当 GCC 编译器运行可执行文件时,会按照如下的路径顺序搜索所需的动态库文件:
- 如果在生成可执行文件时,用户使用了
-Wl,-rpath=dir
(其中 dir 表示要查找的具体路径,如果查找路径有多个,中间用 : 冒号分隔)选项指定动态库的搜索路径,则运行该文件时 GCC 会首先到指定的路径中查找所需的库文件; - GCC 编译器会前往 LD_LIBRARY_PATH 环境变量指明的路径中查找所需的动态库文件;
- GCC 编译器会前往 /ect/ld.so.conf 文件中指定的搜索路径查找动态库文件;
- GCC 编译器会前往默认的搜索路径中(例如 /lib、/lib64、/usr/lib、/usr/lib64 等)中查找所需的动态库文件。
注意,可执行文件的当前存储路径,并不在默认的搜索路径范围内,因此即便将动态库文件和可执行文件放在同一目录下,GCC 编译器也可能提示“找不到动态库”。
因此,对于 GCC 运行可执行文件时提示找不到动态库文件的问题,常用的解决方法是:
- 将动态库文件的存储路径,添加到 LD_LIBRARY_PATH 环境变量中。假设动态库文件存储在 /usr 目录中,通知执行
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr
指令,即可实现此目的(此方式仅在当前命令行窗口中有效); - 修改动态库文件的存储路径,即将其移动至 GCC 编译器默认的搜索路径中。
- 修改~/.bashrc 或 ~/.bash_profile 文件,即在文件最后一行添加
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx
(xxx 为动态库文件的绝对存储路径)。保存之后,执行 source .bashrc 指令(此方式仅对当前登陆用户有效)。
值得一提的是,GCC 编译器提供有 ldd 指令,借助该指令,我们可以明确知道某个可执行文件需要哪些动态库文件做支撑、这些动态库文件是否已经找到、各个动态库文件的具体存储路径等信息。
以《动态链接库的创建和使用》一节中生成的 main.exe 可执行文件为例,执行如下 ldd 指令:
[root@bogon demo]# ldd main.exe
linux-vdso.so.1 => (0x00007fff06fb3000)
libmymath.so => /lib64/libmymath.so (0x00007f65b2a62000)
libc.so.6 => /lib64/libc.so.6 (0x00000037e2c00000)
/lib64/ld-linux-x86-64.so.2 (0x00000037e2800000)
注意,如果某个动态库文件未找到,则 => 后面会显示 not found,表明 GCC 编译器无法找到该动态库,此时该可执行文件将无法执行。
4. makefile
上面把我们常用的gcc用法进行了总结,主要就是静态库和动态库的创建和使用,以及在链接过程中找不到库文件的解决方法。
下面我们就开始将makefile的用法了,makefile文件中包含了一个项目中所有源代码文件的编译规则和依赖关系。
Makefile 文件描述了 Linux 系统下 C/C++ 工程的编译规则,它用来自动化编译 C/C++ 项目。一旦写编写好 Makefile 文件,只需要一个 make 命令,整个工程就开始自动编译,不再需要手动执行 GCC 命令。
一个中大型 C/C++ 工程的源文件有成百上千个,它们按照功能、模块、类型分别放在不同的目录中,Makefile 文件定义了一系列规则,指明了源文件的编译顺序、依赖关系、是否需要重新编译等。
makefile的东西还是很多的,一篇文章肯定是讲不完的。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
译顺序、依赖关系、是否需要重新编译等。
makefile的东西还是很多的,一篇文章肯定是讲不完的。
[外链图片转存中…(img-kYW3M76m-1715631309955)]
[外链图片转存中…(img-460meJg0-1715631309955)]
[外链图片转存中…(img-GzyRK6g7-1715631309956)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新