环境:VS2008
 
我们都知道,链接器在生成可执行程序时,会忽略那些没有用到的符号。但是昨天遇到一个链接问题,看起来与这条基本策略并不相符。首先看一个静态链接库的结构:
 
                lib
|
|---------------------|
a.cpp b.cpp
| |
|-------| |-----------|
fun1 fun2 fun3 fun4
| ↑___________|
↓
GetModuleFileNameEx(psapi.lib)
 
这个库里只存在两个依赖:b.cpp中的fun3依赖于a.cpp中的fun2,a.cpp中的fun1依赖于psapi.lib中的GetModuleFileNameEx。
 
我在一个app中使用了fun4,除此之外别无其它,根据开头提到的策略,显然我并不需要链接psapi.lib。但是事实并非如此,链接器提示了错误:
error LNK2001: 无法解析的外部符号 _GetModuleFileNameExW@16
 
经过反复测试确认,正是fun1所依赖GetModuleFileNameEx导致了链接错误。这看起来很不可思议,fun4对fun1并没有任何依赖关系,链接器为何会报告错误?
 
就我的经验来说,链接器会使用这项策略肯定是毋庸置疑的,最有可能的,是我们存在某个认知错误。所以我决定验证一下,这里的验证分为两部分:
 
一、链接器要求解析一个符号是否意味着链接器需要在生成的PE文件中包含相关符号的代码?
 
遇到LNK2001错误时,我们的第一感觉是链接器需要在生成的程序中包含这个符号,也就是:
1. 如果该符号在静态库中,那么会把该符号相关的代码包含到PE文件中;
2. 如果该符号在动态库中,那么需要把该符号记录到导入表中,以使相关的动态库会在程序启动时加载到进程中并进行地址映射;
 
但是链接器对一个显然没有依赖关系的函数中的符号提示了LNK2001错误,这让我对之前的“感觉”产生了怀疑:或许链接器要解析一个符号并不意味着它会在生成的可执行程序中包含相关的代码。用一个简单的对比试验就能知道结果:
 
#include <windows.h>
#include <psapi.h> #pragma comment(lib, "psapi.lib") void fun_infile_unuse()
{
TCHAR buf[MAX_PATH];
GetModuleFileNameEx(GetCurrentProcess(), NULL, buf, MAX_PATH);
} void fun_infile_use()
{
OutputDebugStringA("fun_infile_use\r\n");
} int _tmain(int argc, _TCHAR* argv[])
{
fun_infile_use();
//fun_infile_unuse(); getchar();
return ;
}
 
首先,在这段程序里,不管有没有调用fun_infile_unuse函数,#pragma comment(lib, "psapi.lib")都是不可缺少的,否则链接器会提示LINK2001错误。
 
但是,为调用与不调用fun_infile_unuse两种情况分别编译两份程序,用LoadPE查看PE文件的导入表,可以看到:只有调用了fun_infile_unuse的那份程序的导出表中存在psapi.dll的条目,另一份程序的导出表中则没有。分别运行两份程序,用process explorer查看它们加载的库列表,也可以看到:没有调用fun_infile_unuse的那份程序,运行起来并不会去加载psapi.dll。 —— 图就不贴了,有兴趣的自己验证。
 
显然,如果在不调用fun_infile_unuse的程序中不会存在psapi.dll的导入表项的话,那么,它也应该不会在程序中包含fun_in_fun_unuse的代码。
 
这证实了我的怀疑。也就是说:链接器需要解析一个符号,并不意味着它真的需要“链接”这个符号。而链接器在真正链接符号生成程序时,确实会遵循“忽略未使用过的符号”的原则。
 
不过这还没有完,当我们得到这个结论后,也就意味着,链接器在查找符号时,并不是按照调用上的依赖关系来进行遍历的。那么,链接器是按照什么关系来遍历符号的呢?这是下一个问题。
 
二、链接器依据怎样的关系来确定要“解析”的符号范围?
 
我不打算对这个问题做严格的推理,只是用几个测试来对比验证我的一个猜想。这些测试用例分别是:
 
.        app
|
main.cpp
|---------|---------|
fun1 fun2 main
| ↑_________|
↓
GetModuleFileNameEx(psapi.lib)
.        app
|
|-----------------|
other.cpp main.cpp
| |
| |---------|
fun1 fun2 main
| ↑_________|
↓
GetModuleFileNameEx(psapi.lib)
.   lib            app
| |
a.cpp main.cpp
| |
|-------| |
fun1 fun2 main
| ↑__________|
↓
GetModuleFileNameEx(psapi.lib)
.              lib                         app
| |
|---------------------| |
a.cpp b.cpp main.cpp
| | |
|-------| |-----------| |
fun1 fun2 fun3 fun4 main
| ↑__________|
↓
GetModuleFileNameEx(psapi.lib)
.              lib                         app
| |
|---------------------| |
a.cpp b.cpp main.cpp
| | |
|-------| |-----------| |
fun1 fun2 fun3 fun4 main
| ↑___________| ↑__________|
↓
GetModuleFileNameEx(psapi.lib)
 
对上述几个用例的测试结果如下:
用例(编号)  :    1        2        3         4        5
是否LINK2001 :    是       是       是         否       是
 
注:在我做的真实测试中,还对命名空间的包含关系做了测试,结果发现没有影响,从命名空间的涵义来说,也不应该有影响,所以相关测试没有列进来。
 
我们都知道,静态链接库是由一组obj组成的,而obj与cpp是一一对应的。所以这里有一个推测:链接器以根据调用关系来搜索符号,但是在处理时是以obj为节点单位的。
1. 对project内的所有cpp(obj),链接器要求解析其中所有使用到的符号。
2. 对外部库,如果使用了其中符号,则定位该符号所在obj,并要求解析该obj中的所有符号。以此类推。
 
完。
 
第二部分是不严谨的测试,也不打算进一步测试,因为需要确认的只是第一部分。如果有对第二部分的内容感兴趣的,那我非常期待看到你的结论:yedaoq@126.com

笔记:LNK2001不代表链接器真的需要链接相关符号的更多相关文章

  1. 【嵌入式开发】 嵌入式开发工具简介 (裸板调试示例 | 交叉工具链 | Makefile | 链接器脚本 | eclipse JLink 调试环境)

    作者 : 韩曙亮 博客地址 : http://blog.csdn.net/shulianghan/article/details/42239705  参考博客 : [嵌入式开发]嵌入式 开发环境 (远 ...

  2. C编译器、链接器、加载器详解

    摘自http://blog.csdn.net/zzxian/article/details/16820035 C编译器.链接器.加载器详解 一.概述 C语言的编译链接过程要把我们编写的一个c程序(源代 ...

  3. Windows应用程序的VC链接器设置

    Windows应用程序的VC链接器设置 /*转载请注明出自 听风独奏 www.GbcDbj.com */ Windows应用程序分为GUI(Graphical User Interface)和CUI( ...

  4. C++之编译器与链接器工作原理

    原文来自:http://blog.sina.com.cn/s/blog_5f8817250100i3oz.html 这里并没不是讨论大学课程中所学的<编译原理>,只是写一些我自己对C++编 ...

  5. C++编译器、链接器工作原理

    1 几个基本概念 编译:编译器对源文件的编译过程,就是将源文件中的文本形式代码翻译为机器语言形式的目标文件的过程,此过程中会有一系列语法检查.指令优化等,生成目标(OBJ)文件. 编译单元:每一个CP ...

  6. C++链接器

    链接器把多个二进制的目标文件(object file)链接成一个单独的可执行文件 在链接过程中,它必须把符号(变量名.函数名等一些列标识符)用对应的数据的内存地址(变量地址.函数地址等)替代,以完成程 ...

  7. C++链接器工具错误:LNK2001, LNK2019(转载)

    这是归属于链接器工具错误 这一类. 无法解析的外部符号“symbol” 代码引用了链接器无法在库和对象文件中找到的内容(如函数.变量或标签). 可能的原因 代码请求的内容不存在(例如,符号拼写错误或使 ...

  8. muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor

    目录 muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor Connector 系统函数connect 处理非阻塞connect的步骤: Connetor时序图 Accep ...

  9. 链接器(linker)的作用——CSAPP第7章读书笔记

    首先说说我为什么要去读这一章.这个学期开OS的课,在Morden Operating System上读到和Process有关的内容时看到这样一句话:“Process is fundamentally ...

随机推荐

  1. C# 常用字符串处理办法

    再基础的东西不常用的话就得记下来...不然就忘记了. C#字符串中特殊字符的转义 一个是双引号",另一个就是转义符\ 对于同样一个字符串:地址:"C:\Users\E.txt&qu ...

  2. LeetCode OJ:Find Median from Data Stream(找数据流的中数)

    Median is the middle value in an ordered integer list. If the size of the list is even, there is no ...

  3. Visual Studio编译与调用DLL方法

    参考自博客:http://www.cnblogs.com/houkai/archive/2013/06/05/3119513.html 用visual studio 2013新建win32 appli ...

  4. Git 分布式版本控制的常见命令

    Git 的作用:  (1)方便多人协同开发; (2)方便版本控制 Git 分布式版本控制的主要区域及命令图,下面会详细讲解: 创建项目并将切换至项目目录下 1. 创建本地仓库:  git init  ...

  5. powershell -enc参数无法解码base64编码payload的解决方案

    powershell的-enc参数允许传入一个base64编码过的powershell脚本字符串作为参数来执行该powershell脚本,该方法常被用于绕过杀毒软件的主动防御机制. 今天下午在做一个后 ...

  6. 多网卡下,vlc发送IGMP组播报告包

    这两天测试IGMP遇到一个问题,环境描述如下: 我的vlc客户端安装在windows下,该PC有两张网卡,本地连接1接公司网,本地链接2 接路由器.wireshark坚挺本地链接2,以测试路由的IGM ...

  7. jquery表单验证插件 jquery.form.js-转

    来自:http://www.cnblogs.com/luluping/archive/2009/04/15/1436177.html Form插件,支持Ajax,支持Ajax文件上传,功能强大,基本满 ...

  8. OGG到OGGAdapter配置详情-从Oracle直接抽取成csv文件

    Oracle Golden Gate是Oracle旗下一款支持异构平台之间高级复制技术,是Oracle力推一种HA高可用产品,简称“OGG”,可以实现Active-Active 双业务中心架构 1.1 ...

  9. 實現多个checkbox單選功能(转)

    <script type="text/javascript">        function check(obj) {            var checkbox ...

  10. 关于Spinlock机制的一点思考

    存在两段代码同时在多核上执行的情况,这时候才需要一个真正的锁来宣告代码对资源的占有. 几个核可能会同时access临界区,这时的spinlock是如何实现的呢? 要用到CPU提供的一些特殊指令,对lo ...