环境:VS2008
 
我们都知道,链接器在生成可执行程序时,会忽略那些没有用到的符号。但是昨天遇到一个链接问题,看起来与这条基本策略并不相符。首先看一个静态链接库的结构:
 
  1. lib
  2. |
  3. |---------------------|
  4. a.cpp b.cpp
  5. | |
  6. |-------| |-----------|
  7. fun1 fun2 fun3 fun4
  8. | ___________|

  9. 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错误,这让我对之前的“感觉”产生了怀疑:或许链接器要解析一个符号并不意味着它会在生成的可执行程序中包含相关的代码。用一个简单的对比试验就能知道结果:
 
  1. #include <windows.h>
  2. #include <psapi.h>
  3.  
  4. #pragma comment(lib, "psapi.lib")
  5.  
  6. void fun_infile_unuse()
  7. {
  8. TCHAR buf[MAX_PATH];
  9. GetModuleFileNameEx(GetCurrentProcess(), NULL, buf, MAX_PATH);
  10. }
  11.  
  12. void fun_infile_use()
  13. {
  14. OutputDebugStringA("fun_infile_use\r\n");
  15. }
  16.  
  17. int _tmain(int argc, _TCHAR* argv[])
  18. {
  19. fun_infile_use();
  20. //fun_infile_unuse();
  21.  
  22. getchar();
  23. return ;
  24. }
 
首先,在这段程序里,不管有没有调用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的代码。
 
这证实了我的怀疑。也就是说:链接器需要解析一个符号,并不意味着它真的需要“链接”这个符号。而链接器在真正链接符号生成程序时,确实会遵循“忽略未使用过的符号”的原则。
 
不过这还没有完,当我们得到这个结论后,也就意味着,链接器在查找符号时,并不是按照调用上的依赖关系来进行遍历的。那么,链接器是按照什么关系来遍历符号的呢?这是下一个问题。
 
二、链接器依据怎样的关系来确定要“解析”的符号范围?
 
我不打算对这个问题做严格的推理,只是用几个测试来对比验证我的一个猜想。这些测试用例分别是:
 
  1. . app
  2. |
  3. main.cpp
  4. |---------|---------|
  5. fun1 fun2 main
  6. | _________|

  7. GetModuleFileNameEx(psapi.lib)
  1. . app
  2. |
  3. |-----------------|
  4. other.cpp main.cpp
  5. | |
  6. | |---------|
  7. fun1 fun2 main
  8. | _________|

  9. GetModuleFileNameEx(psapi.lib)
  1. . lib app
  2. | |
  3. a.cpp main.cpp
  4. | |
  5. |-------| |
  6. fun1 fun2 main
  7. | __________|

  8. GetModuleFileNameEx(psapi.lib)
  1. . lib app
  2. | |
  3. |---------------------| |
  4. a.cpp b.cpp main.cpp
  5. | | |
  6. |-------| |-----------| |
  7. fun1 fun2 fun3 fun4 main
  8. | __________|

  9. GetModuleFileNameEx(psapi.lib)
  1. . lib app
  2. | |
  3. |---------------------| |
  4. a.cpp b.cpp main.cpp
  5. | | |
  6. |-------| |-----------| |
  7. fun1 fun2 fun3 fun4 main
  8. | ___________| __________|

  9. 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. golang版并发爬虫

    准备爬取内涵段子的几则笑话,先查看网址:http://www.budejie.com/text/ 简单分析后发现每页的url呈加1趋势 第一页: http://www.budejie.com/text ...

  2. mysql5.6与mysql5.5不同

    1.编译阶段 要明白with与without的区别,选项值分1和0,或者对应为on或off,代表支持与不支持:with的1(on)与without的0(off)是同样的,with的0(off)与wit ...

  3. myeclipes快捷键

    package com.Test02;//alt+shift+s+s 自动创建toString()//ctrl+ shift+ o  自动导包//* alt+ shift +s+o 有参构造//* a ...

  4. LabVIEW中使用GPU进行高性能计算

    项目中需要使用LabVIEW控制NI FPGA board产生控制信号等,使用GPU对采集的数据进行高性能计算,因此方案之一是用Visual Studio设计基于CUDA的GPU并行计算算法代码,然后 ...

  5. Ubuntu12.04 中文输入法设置

    1.ibus输入法 Ubuntu系统安装后已经自带了ibus输入法,在英语环境下默认不启动. 配置ibus自动启动可 以在ubuntu系统菜单上选择System(系统)--- Preferences( ...

  6. [置顶] 滴滴插件化VirtualAPK框架原理解析(二)之Service 管理

    在前一篇博客滴滴插件化框架VirtualAPK原理解析(一)之插件Activity管理 中VirtualAPK是如何对Activity进行管理的,本篇博客,我们继续来学习这个框架,这次我们学习的是如何 ...

  7. 如何制作dll库的API文档,自动生成微软风格的chm文件 Sandcastle Help File Builder 使用方法

    当你开发了一个库的时候,就需要给库开发一个api文档,微软提供了一个C#库的自动生成工具.我在使用的过程中记录了相关的信息,以供大家学习和查阅,如有不正之处,欢迎指出. 首先先下载一个软件,下载地址在 ...

  8. python常用模块之shutil模块

    python常用模块之shutil模块 shutil模块,高级的文件.文件夹.压缩包处理模块 1.shutil.copyfile(src,des[,length]):将文件内容拷贝到另一个文件 In ...

  9. python caffe 在师兄的代码上修改成自己风格的代码

    首先,感谢师兄的帮助.师兄的代码封装成类,流畅精美,容易调试.我的代码是堆积成的,被师兄嘲笑说写脚本.好吧!我的代码只有我懂,哈哈! 希望以后代码能写得工整点.现在还是让我先懂.这里,我做了一个简单的 ...

  10. 有关linux中,<math.h>的调用方法

    h{font-weight:bold;color:green;font-size:105%} p{font-size:100%} linux下C语言程序中,若要用到math.h中的函数(如:sin() ...