C++中的链接器(Linker)
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)相关推荐
- 三、C++ 链接器 linker
cilinking:从C++源码到可执行二进制的过程.compile文件之后进行链接,找到每个符号.函数的位置,并将其链接在一起 每个文件被编译成一个独立的.obj文件作为translation un ...
- 链接器 --- Linker
链接器 1. 背景 对于经常使用 IDE 的开发者,通常点击一个按钮就万事大吉了,这虽然极大简化了过程,但是对于我们C语言这些相对底层的开发者来说非常非常不友好,屏蔽了大量细节,不了解内部细节是非 ...
- C++ 学习之旅(2)——链接器Linker
每一个.cpp文件经过编译之后都会生成对应的.obj文件,然后通过链接器把它们进行链接,最后就可以生成.exe可执行文件了. 在链接过程中,最常见的错误应该是重复定义了,如下例: Log.h void ...
- C++链接器linker
linking是从c++源码到二进制可执行文件的一个过程. 我们编译后会通过一个叫做链接的过程,链接的主要工作是找到每个符号和符号的位置并把它们链接在一起. 我们需要一种方法将这些文件链接到一个程序. ...
- s32ds 路径_S32DS 使用 tips--工程属性配置(编译选项和C编译器、汇编器及链接器设置)...
内容提要 引言 1. 如何打开S32DS应用工程的属性设置 2. 设置Cross Settings 2.1 配置Create flash image 2.2 配置print size 3. 配置Tar ...
- 链接器、链接过程及相关概念解析
文章目录 1. 编译器驱动程序 2. 目标文件 2.1 可重定位目标文件(.o) 2.2 可执行目标文件(无后缀) 2.3 共享目标文件(.dll和.so) 3. 链接器的任务 3.1 符号解析(sy ...
- LLD-LLVM链接器
LLD-LLVM链接器 LLD是LLVM项目中的链接器,是系统链接器的直接替代,并且运行速度比它们快得多.它还提供了对工具链开发人员有用的功能. 链接器按完整性降序支持ELF(Unix),PE / C ...
- arm-gcc链接器和链接脚本
本文主要介绍了链接器和链接脚本的基本内容.主要偏向于入门级以及常见容易混淆的知识点. 1. 链接器介绍 在现在软件工程中,程序一般都比较复杂,通常由多个源文件组成.在编译的过程中会对这些源文件进行汇编 ...
- 链接脚本(Linker Scripts)语法和规则解析(翻译自官方手册)
原链接:链接脚本(Linker Scripts)语法和规则解析(翻译自官方手册)_BSP-路人甲的博客-CSDN博客_链接脚本语法 为了便于与英文原文对照学习与理解(部分翻译可能不准确),本文中的每个 ...
最新文章
- 输入示例,自动生成代码:TensorFlow官方工具TF-Coder已开源
- blob转file对象_JavaScript Blob 对象解析
- 服务器虚拟机网卡怎么配置文件,VMWARE复制虚拟机之后,需重新配置网卡(CENTOS 6)...
- java visualvm分析_使用VisualVM分析性能
- 路遥工具箱全面迁移至 .NET 6.0 并发布 3.0 版本及迁移记录详解
- Unity3D之移植学习笔记:移植到Android平台
- linux系统mount命令挂载windows系统共享文件夹
- 收藏一个好看的单选多选样式
- python确定样本量(总体均值)
- 设置程序在Windows开机后自动运行的方式
- linux查看日志内存,linux查看日志、磁盘、cpu、内存使用情况及清理磁盘,日志等。你需要的linux常用基本操作都在这里!!!...
- C语言每日一练——第61天:掷骰子游戏
- android夜间模式监控
- 解决gitlab-runner一直处于等待中
- python程序运行时间的几种分析方法
- easyui的简单实例
- android 微信摇一摇代码,Android微信摇一摇
- 算法工程师0——算法工程师学习进阶路线
- scada java_SCADA开源项目lite版本
- 【Redux 和 React-Recux】
热门文章
- 小程序制作开发,让梦想照进现实!
- 2.5 TCP/IP命令(flushdns)
- vs2017出现“Failed to load required native libraries. Have you installed the latest LibVLC packag”解决办法
- sql中deny的使用
- strokeRect函数使用
- Portia---一款开源可视化爬虫工具
- 华为三层交换机实现不同vlan间通信
- 台灯哪个牌子的比较好保护视力的?推荐五款护眼台灯
- php与flex宝典
- [转]李维《Delphi 2006 高效数据程序设计——dbExpress 篇》连载