gcc/g++动/静态编译/连接
前述
声明:尽管C/C++属于高级语言、可移植代码,但是本文的上下文环境是
Redhat Enterprise Linux 7.1
在不同*nix之间也许会有些许差异,但大体相同;与Windows环境可能会有一些不同,但理念相同。
注明:
个人觉得讲动静态编译
不是很合适,严格来说编译
是从.c
到.s
的过程,而链接才是从.o
到可执行文件的过程。另外,很多程序是动静态链接结合起来生成/运行的。当然,如果非要下一个定义的话,我认为动态编译
指创建使用了动态链接库的程序,静态编译
指创建完全使用静态文件(.o或.a)生成,不依赖于运行时库的程序。
C/C++程序的翻译具有典型的过程:
- 预编译 -> 编译 -> 汇编 -> 链接
文章标题很明确地提出了四个主要问题:
- 如何生成一个静态链接库文件?(How)
- 如何生成一个动态链接库文件?(How)
- 如何使用一个静态链接库文件?(How)
- 如何使用一个动态链接库文件?(How)
另外,挖掘一下上下文,我们还可以提出两个问题:
- 为什么要使用/生成静态链接库?(Why)
- 为什么要使用/生成动态链接库?(Why)
再往上:
- 什么是静态链接(库)?(What)
- 什么是动态链接(库)?(What)
在前述部分,我们先讨论一下后四个问题。
What
// all.c
#include <stdio.h>
int main()
{
printf("Hello world\n");
return 0;
}
gcc -o all all.c
如上。gcc根据头文件stdio.h
和符号printf
,将”printf”函数的引用解析到libc.so
库中,在./all
运行时动态地链接到libc.so
共享库,这就是动态链接。可以看出,all.c
源代码中不包含printf
函数的定义,也没有其他地方显式地定义printf
。
*.so
是动态链接库。
gcc -c print.c
gcc -o hello main.c print.o
如上。第一条命令先生成了print.o
,第二条会把main.c
中对Print
符号的引用解析到print.o
中,这个动作是在编译时进行的,即静态链接(当然,print.o
对于printf
符号的引用是依赖于动态链接的)。
把多个.o
文件打包成.a
文件,这就是静态库。
无论动态还是静态,链接过程最重要的概念是符号解析和重定位。
Why
软件工程中的很多措施都是为了降低开发大型程序的复杂性。
对于大型工程来说,直接采用多个源文件到多个.o
文件到一个可执行文件的创建方法有很多问题。一方面是管理混乱,一方面是每次升级程序都要重新生成整个工程,另一方面,这会导致程序的体积过于庞大。
静态链接库解决了一部分问题。通过将多个.o
文件打包成一个.a
文件,方便了管理;将每个标准函数放在一个.o
文件中而不是把所有标准函数放在同一个libc.o
文件中,链接器只拷贝被程序引用的目标模块,大大减小了空间占用,也在一定程度上体现了”高内聚,低耦合“的原则。
然而,静态链接不能完美解决磁盘占用问题,不能解决内存占用的问题。
静态链接的缺点往往是动态链接的优点,反之亦然。
在文件系统中,一个库只有一个.so
文件,所有引用该库的可执行文件都共享这个.so
,不必拷贝到可执行文件中,这大大减小了可执行文件的体积;另外,在内存中,一个共享库的.text
节(基本相当于代码段)的一个副本可以被不同的进程共享,这减少了重复的内存占用。另外,为了更新/升级程序,开发者只需要更新升级对应的动态链接库即可,不必重新生成整个工程。
很明显,动态编译产生的程序体积要小于静态编译产生的程序,有时候会远远小于。
但是,依赖于动态库的可执行文件在系统缺少相应动态库时是不可以运行的。
具体地,可以通过ldd ./xxx
来查看某可执行文件依赖于哪些动态库。
可以通过file ./xxx
来查看某可执行文件是动态链接还是静态链接(从ldd的返回结果也可以看出)。
如何生成/使用一个静态链接库文件?
一般来说,生成一个静态库文件的命令如下:
gcc -c xxx1.c xxx2.c xxx3.c
ar rcs xxx.a xxx1.o xxx2.o xxx3.o
使用静态库文件的命令如下:
gcc -c main.c
gcc -static -o main main.o ./xxx.a
另外,通过把库搜索路径添加到环境变量LD_LIBRARY_PATH
中,也可以直接使用-lxxx
的形式。
如何生成/使用一个动态链接库文件?
一般来说,生成一个动态库文件的命令如下:
gcc -shared -fPIC -o xxx.so ./xxx.c
使用动态库文件的命令如下:
gcc -o main ./main.c ./xxx.so
动态库也可以通过修改环境变量的方式指定库路径。
诸如libc.so
和libstdc++.so
之类的通用库通常不需要在命令中指出具体位置(前提是你的系统上对应目录内得有)。
另外,
gcc -print-search-dirs
可以查看gcc默认的搜索路径。
gcc/g++/mysql API静态编译配置(说白了,使用静态库)
gcc/g++静态编译配置
涉及到的库:
glibc-static
libstdc++-static
libmysqlclient.a
我的第一次作业里配置好的环境并不可以直接进行gcc的静态编译,因为libc.a虽然存在于系统中,却不在ld搜索的路径内。
第一步,把它放到LD_LIBRARY_PATH
指定的路径内,然后保证gcc -o xxx xxx.c -static
先成功。再解决g++:下载对应版本的libstdc++-static.rpm
。注意选择版本,我下载的是libstdc++-static-4.8.2-16.el7.x86_64.rpm
,可能与我之前安装了libstdc++-devel-4.8.2-16.el7.x86_64.rpm
有关。然后rpm -i ./libstdc++-static-4.8.2-16.el7.x86_64.rpm
。此时搜索libstdc++.a
就有了,同样放到放到LD_LIBRARY_PATH
下,g++ -o xxx xxx.cpp -static
成功。
另外,可以通过file ./xxx
来判断一个可执行文件是动态链接还是静态链接的。
mysql API静态编译
一般来说,要进行依赖某库的静态编译,需要加上-static -lLIBNAME
这样的参数。
之前在做c语言调用mysql API
时,起关键作用的库是libmysqlclient.so
。这里做静态当然希望有一个libmysqlclient.a
了。在网上搜索一下,倒是有 libmysqlclient.a 的信息,却没有提供下载的地方。知乎上有人说自己编译一次mysql source code
就好,试试。
先mysql --version
查看了一下版本:5.5.41
,于是下载对应版本源码。在 mariadb 官网得知 5.5.x 版本以后都采用cmake
安装,于是大概看了cmake
的用法。安装前需要配置一些基本的环境,这个在源码目录下的cmake
配置说明中有列出,当然也可以先cmake ./
试一下,成功了,继续make
,make install
。之后updatedb
,locate libmysqlclient.a
,在源代码目录下的./libmysql/
下找到!
于是将这个东西放到/usr/lib64/
下,在LD_LIBRARY_PATH
环境变量中加上这个路径,给g++
加上-lmysqlclient
,编译,然而还不行,根据提示信息,少了一个好像叫pthread
的东西,在网上搜一下,也是个库,locate libpthread.a
,有!放到/usr/lib64
下,再加上-lpthread
,编译,还是不行。。。似乎少了一个与dl
有关的东西。locate libdl.a
,又有!也放到/usr/lib64
下,再来-ldl
。这次报了几个警告。大意是说可能会依赖glibc中的一些函数。好歹成功了,运行一下,提示缺少/tmp/mysql.sock
,但是一般这个文件在/var/lib/mysql/
下,所以再ln -s /var/lib/mysql/mysql.sock /tmp/mysql.sock
。再次运行,成功~
补充
Linux下静态库/动态库的对应关系
一般而言,动态库名为xxx.so,取shared objects
,即共享目标文件之意;静态库名为xxx.a,取archives
,即档案之意。例如,libc.a
和libc.so
。
本文相关gcc选项的说明
若无特殊说明则摘自man gcc
-lLIBRARY
在链接时搜索名为LIBRARY
的库。也可以写成-l LIBRARY
形式,但是这种形式只对遵守POSIX标准的程序有效,所以不推荐。
链接器根据你输入命令的顺序搜索库。例如,foo.o -lz bar.o
,链接器在foo.o
之后、bar.o
之前搜索库,所以如果bar.o
需要z
中的库,则会报错。
-Ldir
将dir目录添加到-l
选项搜索的路径中。
-static
在支持动态链接的系统上,该选项阻止与共享库链接。在不支持动态链接的系统上,这个选项没有什么用处。
-shared
告诉gcc生成一个共享库文件。使用该选项时一定要同时附加其他如-fpic
,-fPIC
之类的选项。
-fPIC
指示gcc生成与位置无关的代码(深入理解这一点需要对符号解析和重定位有了解,具体可参考延伸阅读的资料)。
其他
对于Linux下链接过程的深入讨论不得不涉及到ELF文件格式,以及符号解析和重定位的问题,可以参考延伸阅读部分的资料。
延伸阅读
《深入理解计算机系统》第二版 第七章
《程序员的自我修养》
Linkers & Loaders
Linux下man gcc
;或者gcc官方手册也很棒(很长,可以挑着看)
ELF文件格式标准