C++链接器

Linking

Linking是C++从源码到执行二进制时的一个过程。它的主要工作是找到每个符号和函数的位置并且将它们链接在一起。当我们在多个C++文件中写代码,并且想要将这些文件链接到一个程序,这就是链接器的主要目的。

编译有两个阶段——编译和链接。在VS中,按下Ctrl + F7或者“编译”按钮,只有编译会发生,链接完全不会发生。当build项目或者按F5运行时它会编译然后链接。
下面的程序只有两个函数,没有主函数:

#include<iostream>void Log(const char* message)
{std::cout << message << std::endl;
}int Multiply(int a, int b)
{return a * b;
}

按下Ctrl+F7编译时不会产生任何报错行为。
但是如果Build项目,就会产生Linking错误,因为缺少程序入口(Main函数):

注意:程序入口点不一定非得是Main函数(可以自行修改成别的,一般没人改),但是必须要有一个入口点

编译器可能会遇到的一种链接错误(unresolved external symbol 未解决的外部符号)

当链接器找不到它需要的东西时,就会发生这种情况。
如下面的程序:
Log.cpp

#include<iostream>void Log(const char* message)
{std::cout << message << std::endl;
}

main.cpp

#include<iostream>void Log(const char* message);int Multiply(int a, int b)
{Log("Multiply");return a * b;
}int main()
{std::cout << Multiply(5, 8) << std::endl;return 0;
}

如果将Log.cpp文件中的函数名由Log改为Logr,重新编译,此时还会编译成功,因为在编译阶段没有链接,编译器做的只是检查能确保正确编译,由于在main.cpp中有Log()函数的声明,所以它坚信某处肯定存在一个Log()函数
但是如果build整个项目,就会得到一个**LNK2019(unresolved external symbol)**的错误:

我们在Multiply()函数中调用了Log()函数,但是链接器找不到那个函数,所以它必须返回给我们一个错误。

此时如果在Multiply()函数中注释掉Log(“Multiply”)
Log.cpp

int Multiply(int a, int b)
{//Log("Multiply");return a * b;
}

这样永远没人调用Log()函数,此时再重新Build,没有报错
之所以不报错,是因为我们从未调用过Log()函数,所以linker不需要通过这个链接这个函数来调用Log()函数
有趣的是,如果在Multiply()函数中保留Log(“Multiply”),而在main()函数中注释掉Multiply(5, 8)
main.cpp

#include<iostream>void Log(const char* message);int Multiply(int a, int b)
{Log("Multiply");return a * b;
}int main()
{//std::cout << Multiply(5, 8) << std::endl;return 0;
}

这样同样也没有调Log()函数,但是如果Build项目,会得到LNK2019(unresolved external symbol)的错误。
因为我们虽然没有在这个文件中使用Multiply()函数,技术上来讲不能保证我们在别的文件中也不调用它,所以Linker需要链接它
如果我们有办法告诉编译器Multiply()函数只会在当前文件中使用,就可以消除这种linking的必要。
实现方法很简单:在Multiply()函数前面加一个static关键字:

static int Multiply(int a, int b)
{Log("Multiply");return a * b;
}

这意味着Multiply()函数只是为这个编译单元声明的,此处是main.cpp,这样子Multiply()函数就不会在别的文件中被调用,现在也没有在main.cpp中调用,所以此时build不会有任何linking错误。

static修饰函数时,表明该函数只在同一文件中调用

编译器可能会遇到的另一种链接错误(两个函数具有相同的函数名或者相同的签名)

当linker碰到这种问题,它不知道该link哪个函数,就会报错。
如:

#include<iostream>void Log(const char* message)
{std::cout << message << std::endl;
}void Log(const char* message)
{std::cout << message << std::endl;
}

当我们build项目时,会得到一个编译错误(C2084),因为重名的函数在一个文件中,可以被编译器察觉,所以这种情况会很容易被编译器发现并警告,不需要链接发生。
下面这种情况很危险,不容易被发现
Log.h

void Log(const char* message)
{std::cout << message << std::endl;
}

Log.cpp

#include<iostream>
#include"log.h"void InitLog()
{Log("Initialized Log");
}

Main.cpp

#include<iostream>
#include"log.h"int Multiply(int a, int b)
{Log("Multiply");return a * b;
}int main()
{std::cout << Multiply(5, 8) << std::endl;return 0;
}

简单的说,就是现在既在main.cpp里调用Log(),也在log.cpp中调用Log(),现在build项目会出现LNK2005的错误,提示Log()已在log.obj中定义。但是我们明明只在log.h文件中定义了Log(),只有一个定义,为什么会这样呢?
原因归咎于#include,因为在include一个头文件时,编译器预处理会把头文件的全部内容粘贴在#include语句的位置,上面的写法实际上是把Log()函数的定义复制粘贴进log.cpp和main.cpp中。这也正是头文件里不能放函数定义,只放函数声明,最多放带有inline关键字的内联函数的原因。

解决上述问题的方法也不唯一
方法一:使用static关键字。对log.h进行修改,修改如下:
Log.h

static void Log(const char* message)
{std::cout << message << std::endl;
}

这样修改意味着对这个Log()函数链接时链接只应该发生在该文件内部,换句话说,当这个Log()函数被include在log.cpp和main.cpp时,只会对该文件内部有效,log.cpp和main.cpp会有自己版本的Log()函数,对其他任何obj都是不可见的。

方法二,使用inline关键字。对log.h进行修改,修改如下:
Log.h

inline void Log(const char* message)
{std::cout << message << std::endl;
}

inline的意思是直接把函数的身体拿来取代调用。这样写相当于把log.cpp变成了如下形式:
Log.cpp

#include<iostream>
#include"log.h"void InitLog()
{std::cout << "Initialized Log" << std::endl;
}

方法三:将Log()函数的定义移动到一个编译单元中,log.h里面只放声明。这也是大多数情况下会做的方法。
Log.h

void Log(const char* message);

Log.cpp

#include<iostream>
#include"log.h"void Log(const char* message)
{std::cout << message << std::endl;
}
void InitLog()
{Log("Initialized Log");
}

C++中的链接器(Linker)相关推荐

  1. 三、C++ 链接器 linker

    cilinking:从C++源码到可执行二进制的过程.compile文件之后进行链接,找到每个符号.函数的位置,并将其链接在一起 每个文件被编译成一个独立的.obj文件作为translation un ...

  2. 链接器 --- Linker

    链接器 1. 背景 ​ 对于经常使用 IDE 的开发者,通常点击一个按钮就万事大吉了,这虽然极大简化了过程,但是对于我们C语言这些相对底层的开发者来说非常非常不友好,屏蔽了大量细节,不了解内部细节是非 ...

  3. C++ 学习之旅(2)——链接器Linker

    每一个.cpp文件经过编译之后都会生成对应的.obj文件,然后通过链接器把它们进行链接,最后就可以生成.exe可执行文件了. 在链接过程中,最常见的错误应该是重复定义了,如下例: Log.h void ...

  4. C++链接器linker

    linking是从c++源码到二进制可执行文件的一个过程. 我们编译后会通过一个叫做链接的过程,链接的主要工作是找到每个符号和符号的位置并把它们链接在一起. 我们需要一种方法将这些文件链接到一个程序. ...

  5. s32ds 路径_S32DS 使用 tips--工程属性配置(编译选项和C编译器、汇编器及链接器设置)...

    内容提要 引言 1. 如何打开S32DS应用工程的属性设置 2. 设置Cross Settings 2.1 配置Create flash image 2.2 配置print size 3. 配置Tar ...

  6. 链接器、链接过程及相关概念解析

    文章目录 1. 编译器驱动程序 2. 目标文件 2.1 可重定位目标文件(.o) 2.2 可执行目标文件(无后缀) 2.3 共享目标文件(.dll和.so) 3. 链接器的任务 3.1 符号解析(sy ...

  7. LLD-LLVM链接器

    LLD-LLVM链接器 LLD是LLVM项目中的链接器,是系统链接器的直接替代,并且运行速度比它们快得多.它还提供了对工具链开发人员有用的功能. 链接器按完整性降序支持ELF(Unix),PE / C ...

  8. arm-gcc链接器和链接脚本

    本文主要介绍了链接器和链接脚本的基本内容.主要偏向于入门级以及常见容易混淆的知识点. 1. 链接器介绍 在现在软件工程中,程序一般都比较复杂,通常由多个源文件组成.在编译的过程中会对这些源文件进行汇编 ...

  9. 链接脚本(Linker Scripts)语法和规则解析(翻译自官方手册)

    原链接:链接脚本(Linker Scripts)语法和规则解析(翻译自官方手册)_BSP-路人甲的博客-CSDN博客_链接脚本语法 为了便于与英文原文对照学习与理解(部分翻译可能不准确),本文中的每个 ...

最新文章

  1. 输入示例,自动生成代码:TensorFlow官方工具TF-Coder已开源
  2. blob转file对象_JavaScript Blob 对象解析
  3. 服务器虚拟机网卡怎么配置文件,VMWARE复制虚拟机之后,需重新配置网卡(CENTOS 6)...
  4. java visualvm分析_使用VisualVM分析性能
  5. 路遥工具箱全面迁移至 .NET 6.0 并发布 3.0 版本及迁移记录详解
  6. Unity3D之移植学习笔记:移植到Android平台
  7. linux系统mount命令挂载windows系统共享文件夹
  8. 收藏一个好看的单选多选样式
  9. python确定样本量(总体均值)
  10. 设置程序在Windows开机后自动运行的方式
  11. linux查看日志内存,linux查看日志、磁盘、cpu、内存使用情况及清理磁盘,日志等。你需要的linux常用基本操作都在这里!!!...
  12. C语言每日一练——第61天:掷骰子游戏
  13. android夜间模式监控
  14. 解决gitlab-runner一直处于等待中
  15. python程序运行时间的几种分析方法
  16. easyui的简单实例
  17. android 微信摇一摇代码,Android微信摇一摇
  18. 算法工程师0——算法工程师学习进阶路线
  19. scada java_SCADA开源项目lite版本
  20. 【Redux 和 React-Recux】

热门文章

  1. 小程序制作开发,让梦想照进现实!
  2. 2.5 TCP/IP命令(flushdns)
  3. vs2017出现“Failed to load required native libraries. Have you installed the latest LibVLC packag”解决办法
  4. sql中deny的使用
  5. strokeRect函数使用
  6. Portia---一款开源可视化爬虫工具
  7. 华为三层交换机实现不同vlan间通信
  8. 台灯哪个牌子的比较好保护视力的?推荐五款护眼台灯
  9. php与flex宝典
  10. [转]李维《Delphi 2006 高效数据程序设计——dbExpress 篇》连载