环境: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. section和div

    section和div 一.DIV div即division(区块),把文档分割为独立的.不同的部份.作用,以下三种情况应该用div标签: 1.用于页面布局,且不是 header.footer 之类的 ...

  2. Spark安装和简单示例

    spark的安装 先到官网下载安装包 注意第二项要选择和自己hadoop版本相匹配的spark版本,然后在第4项点击下载.若无图形界面,可用windows系统下载完成后传送到centos中. 本例中安 ...

  3. Linux下保存文件

    经常需要dump数据,mark一下 #if 0 FILE * fd = NULL; fd = fopen("/mnt/raw.pcm", "ab+"); if ...

  4. MySQL在本机无法基于localhost访问的问题解决

    引言: 在本地访问数据库之时,一般使用localhost, 127.0.0.1来进行数据库访问,但是笔者这几天就碰到了只能通过127.0.0.1来访问,但是无法基于localhost来访问,非常之诡异 ...

  5. caffe学习4——net

    参考文献 1 The net jointly defines a function and its gradient by composition and auto-differentiation. ...

  6. rest-framework框架 -- 认证权限流程源码

    认证权限 解析BaseAuthentication源码 # 做认证权限 from rest_framework import exceptions from ..models import * cla ...

  7. 06----fiter

    步骤一:渲染标签 双层for循环---- 步骤二:进行数据过滤 fiter_name=java 知识点: """ 改为生成器方式: def foo(): temp=[] ...

  8. stark组件

    写一个stark组件仿造admin的功能 1:新建一个stark的app 问题:在django每次启动会扫描目录下所有的admin文件,需要扫描项目目录下的每个stark文件,我们需要怎么做 1:看在 ...

  9. [剑指offer]数组中最小的K个数,C++实现

    原创博文,转载请注明出处! http://github.com/wanglei5205 http://cnblogs.com/wanglei5205 # 题目 输入n个整数,找出其中最小的K个数.例如 ...

  10. Eclipse自动补全设置与Eclipse源代码下载

    以前使用VisualStudio和Sublime写代码的时候有很完善的代码提示,但是最近开始使用Eclipse弄JAVA的时候发现它的代码提示不是很习惯.上网找了一些资料,修改了代码提示的方式,记录在 ...