环境: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. LeetCode OJ:Restore IP Addresses(存储IP地址)

    Given a string containing only digits, restore it by returning all possible valid IP address combina ...

  2. NumberFormat

    package com.NumberFormat; import java.text.NumberFormat; public class Study01 { public static void m ...

  3. Javascript-- jQuery动画篇(2)

    动画效果 前面的 hide/show,slide in/out 其实也具有动画效果,本篇介绍使用 animate()实现自定义动画效果. 基本语法如下: $(selector).animate({pa ...

  4. Join, Group Join

    Linq的 Join对应SQL中的inner join,当左右两张表有匹配数据时才返回一条记录: Linq的 Group Join对应SQL中的LEFT OUTER JOIN,即使右表中没有匹配,也从 ...

  5. mysql连接踩坑

    本机安装的是wamp,集成了mysql.php.apache.安装了sqlyog客户端. 1.错误代码2003 证明mysql服务没有开启,此时需要开启mysql服务,开启了wamp 2.错误代码10 ...

  6. Unity3D 海水多线程渲染算法实现

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...

  7. tomcat的安装和配置

    本人java开发的菜鸟工程师,这几天学习了tomcat的安装和使用,终于在今天运行成功. 一.tomcat的安装 1.tomcat下载网址:http://tomcat.apache.org/ 2.打开 ...

  8. NodeJs 基础知识

    1.网站 http://nodejs.cn/ 下载最新NodeJs并且安装2. 你可以输入一个新命令“node”.使用该“node”命令有两种不同的方法.第一种不带任何参数,将打开一个交互式Shell ...

  9. Objective-C-----协议protocol,代码块block,分类category

    概述 ObjC的语法主要基于smalltalk进行设计的,除了提供常规的面向对象特性外,还增加了很多其他特性,本文将重点介绍objective-C中一些常用的语法特性. 当然这些内容虽然和其他高级语言 ...

  10. learn go function callback

    package main // 参考文档: // https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/06.7.md im ...