笔者一直以来都对mingw64下动态库和静态库链接的真正区别和用法存疑,于是做了一些测试,这篇文章记录了测试过程和测试结果,如果只想知道结果可以跳转到文章末尾
首先准备三个测试文件
其中add.c最终会被编译会静态库或动态库,被测试文件main.c调用
使用如下命令构建静态库
gcc -c add.c -o add.o
ar rcs libadd.a add.o
得到静态库如下:
接着使用如下命令构建动态库
gcc -fPIC -shared add.c -o libadd.dll
得到动态库如下:
首先把五个文件放在同一路径下
已知在编译指令中,链接libadd.a 和链接libadd.dll都只需要加上以下参数
-ladd
也即这里静态库和动态库可以看做是重名的,于是我们先来测试当输入-ladd的时候,gcc会默认链接哪个库
输入指令:
gcc main.c -I ./ -L ./ -ladd -o normal_two_implicit
得到文件
文件名是为了对比实验方便而设置的,其中normal意味着正常状态,与后面要进行的反常测试相对应,two表示静态库和动态库同时位于同一文件夹下,与之后将某个库移走单独测试剩下的库相对应,implicit意为在链接命令中隐式指出库名称(即-l格式的指明方法),与之后显示给出库的全称的测试对应。
虽然现在得到了可执行文件,但是现在我们还无法判断默认链接的是哪一个库,于是继续进行测试
先把libadd.dll暂时移到其他文件夹里,只保留libadd.a,再键入同样的命令:
gcc main.c -I ./ -L ./ -ladd -o normal_a_implicit
得到文件
这里的exe一定链接的是静态库,而且和上一个exe大小完全相同,因此合理推测前一个exe也是链接了静态库,也即:当两种同名库同时存在于同一文件夹,并且使用-l格式参数隐式指出链接库名称时,gcc优先链接后缀为.a的库(为什么不说优先链接静态库?后续的测试会说明)
然后把libadd.a暂时移到其他文件夹里,只保留libadd.dll,再键入同样的命令:
gcc main.c -I ./ -L ./ -ladd -o normal_dll_implicit
得到文件
文件大小只有54KB,小于前两个,因此这是个链接了动态库的exe
因此得到结论:当一个静态库和一个动态库重名,并且位于同一文件夹下,编译器优先链接后缀.a的库
之前一直以为,加了-static就是静态链接,做出来的exe一定就是那种完全可以移植的,但是经过测试以后我发现我大错特错
首先把libadd.a和libadd.dll放回原位
然后输入以下命令:
gcc -static main.c -I ./ -L ./ -ladd -o normal_two_implicit_static
得到:
文件大小为64KB,这说明不出所料地,编译器优先链接了静态库,这和-static关键字很搭,接下来把libadd.dll暂时移走,只留下libadd.a,输入以下命令
gcc -static main.c -I ./ -L ./ -ladd -o normal_a_implicit_static
得到
64KB,链接了静态库,依然在意料之中
这次移走libadd.a,只留下libadd.dll,然后输入以下命令
gcc -static main.c -I ./ -L ./ -ladd -o normal_dll_implicit_static
错误出现了:
错误信息为并没有找到库文件!这和我们的预设也一致,因为-static禁用了动态库,所以不再能找得到动态库dll
测试至此本可以结束,但是这里仍然存在一个疑问:我们到目前为止还没有通过显式的指出文件名来链接库,因为其结果似乎不用想都可以知道,那就是给出什么文件名就链接哪个文件,不可能出现重名的情况,这当然是对的,但是在-static关键字的加持下,似乎在另一个角度出现了有意思的现象
再一次把文件归位在一起
键入如下命令:
gcc -static main.c -I ./ -L ./ libadd.a -o normal_a_explicit_static
得到文件
没有问题
然后键入如下命令
gcc -static main.c -I ./ -L ./ libadd.dll -o normal_dll_explicit_static
根据之前的经验,我猜测:由于-static禁用了动态库链接的选项,那么这条命令应该也会失败并报错,但是奇怪的事情发生了,命令正常运行并且得到了正常的动态库链接exe(因为它的大小为54KB)
至此我们得到了第二个十分有用的结论:-static并不禁用动态链接,它只是禁用了采用隐式指明库名法(即以-l格式指明库名)时动态链接的选项,如果你愿意的话完全可以在有-static的情况下输入完整的动态库dll的全称来进行动态链接
这个疑问乍看起来有些多次一举:后缀为.a的就是静态库,后缀为.dll的就是动态库呀。
但是事实真的如此吗?我们完全可以通过修改后缀名而保持它们的二进制数据并不变化,修改以后真正的静态库后缀为.dll,而真正的动态库后缀为.a,。在这种情况下如果将.a链接进去,编译器会辨别出它实际上是个披着静态库外皮的动态库呢,还是会傻傻地将其当做静态库打包进exe?于是有了下面的测试:
修改后缀名的过程略去
经过修改后缀名,现在这里的.a才是真正的动态库,而.dll才是真正的静态库
键入以下命令:
gcc main.c -I ./ -L ./ -ladd -o unnormal_two_implicit
得到文件
文件只有54KB,这说明它实际上链接了动态库,也即现在的.a库,这与我们测试1中的结论也相同,即:当同名库同时存在时,隐式指明库名会默认优先链接.a库(这也是为什么前面不说优先链接静态库的原因,因为.a不一定是静态库,在本例中它是一个披着静态库后缀的动态库)
于是得到有用的第三条结论:编译器靠二进制内容识别静态库和动态库,而不会被其后缀名所迷惑
1.当一个静态库和一个动态库重名,并且位于同一文件夹下,编译器优先链接后缀.a的库
2.-static并不禁用动态链接,它只是禁用了采用隐式指明库名法(即以-l格式指明库名)时动态链接的选项,如果你愿意的话完全可以在有-static的情况下输入完整的动态库dll的全称来进行动态链接
3.编译器靠二进制内容识别静态库和动态库,而不会被其后缀名所迷惑
下面以编程人员的角度,根据以上三条结论再导出出几条实用结论:
Q:当需要把一个dll链接进程序的时候该怎么做?
A:在不确定是否有重名库的情况下,建议直接在编译参数中显式给出库名的全称,如果确定没有重名静态库的话,可以考虑使用-l的参数指出dll的名称
Q:当需要进行静态编译,即把所有库打包放进exe以保证其良好的不受环境依赖性的时候该怎么做?
A:只加一条-static并不会完事大吉,建议仔细检查给出的库中不包含动态库,然后加上-static参数作为最终检查
Q:我要开发一个程序,程序运行时需要调用一些第三方动态库如opencvworld455.dll,但是我又希望我编译出的exe可以单独发布出去让其他人运行,可以做到把opencvworld455.dll静态链接到exe中吗?
A:不能这么做,动态库无法静态链接,这是库的二进制内容差异决定的,要发布依赖第三方dll的可执行文件exe,必须同时打包发布其依赖的第三方dll(有些商业软件为了保证在一些连基本dll环境如msvcr.dll都没有的机器上运行,甚至会将全部dll和exe一起打包发布)
1.本文省略了一些与主题相关性不高的测试过程,比如测试第一个问题,重名问题时,并没有给出显式指明库名的结果,以及第三个问题,编译器靠什么识别一个库时静态库还是动态库问题中,也没有给出加上-static之后的测试结果,另外仅凭文件大小就判断链接了静态库和动态库未免有些草率,这些问题都从某种程度上显得测试的逻辑性不够严密。但实际上笔者几乎做了一切可以想到的测试,并且通过移走dll再运行exe发现不能运行才判断其为动态链接产物,发现能运行才判断其为静态链接产物,这些测试结果由于结果显而易见和篇幅限制(懒)的缘故在文章中没有给出,感兴趣的读者可以自己试试
2.本文测试的平台为mingw64+windows10,结论并不一定适用于其他平台
3.如有错误,欢迎指正