栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

三、C++ 链接器 linker

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

三、C++ 链接器 linker

cilinking:从C++源码到可执行二进制的过程。compile文件之后进行链接,找到每个符号、函数的位置,并将其链接在一起

每个文件被编译成一个独立的.obj文件作为translation unit,这些文件无法自主沟通。写一个程序使用到了多个文件时,使用linker将这些文件链接到一个程序。(<——链接器的工作)。

即使没有外部文件里的函数(仅有一个文件),应用程序为了直到入口点在哪(main函数在哪,程序运行时从main函数开始)

编译有两个阶段:编译与链接

区分方法:ctrl+F7 或 build(编译)文件,仅有编译发生,未发生链接

build整个项目之后,或F5运行时 先编译后链接

编译器complier处理语法错误 C开头, 表示编译阶段发生的错误

链接器linker处理链接错误 LNK开头, 表示链接阶段发生的错误

令Source Files文件夹下仅有Math.cpp文件,

Math.cpp

#include 

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

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

 此时build整个项目会报错LINK:fatal error LNK1561:entry point must be defined

        原因:缺少主函数

 .exe文件必要要有一个切入点

该切入点不一定非得是main函数,但是必须要有切入点

在Math.cpp函数中添加main函数,编译整个项目成功(linker)生成.exe文件,此时Math.cpp

#include 

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

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

int main()
{

}

 再次修改Math.cpp文件

#include 

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

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

int main()
{
	std::cout << Multiply(5,8) << std::endl;
	std::cin.get();//该语句确保控制台不会被瞬间关闭
}

输出

假设这些程序存在于多个文件中:(将重新添加Log函数.cpp文至Source Files),将Math.cpp文件中的Log函数移至Log.cpp文件在,重新编译Math.cpp会得到C开头的error,没有找到Log,原因是Math.cpp不知道Log函数的存在。Math.cpp文件中并未添加Log函数的声明

将Log函数的首行添加至Math.cpp中进行声明,再次编译显示成功

此时build整个项目,会报compile error

 原因:Log函数中并未include

Log函数中include 后,项目compile(linker)成功

Math.cpp

#include 

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;
	std::cin.get();//该语句确保控制台不会被瞬间关闭
}

Log.cpp

#include  

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

常见的linking error(链接错误)

①uresolved external symbol(未解决的外部符号)

原因:链接器找不到需要的东西

例如,此时将Log文件中"void Log(const char* message) "Log进行改变,Math.cpp文件中仍然有Log函数的声明,文件仍然会被compile编译(因为没有linker,文件只检查以确保能够正确编译,相信某处有Log函数,但是找到这个函数是链接阶段的工作),此时compile整个项目(linker),会得到linking错误,uresolved external symbol(未解决的外部符号),那个符号缺失了(此处为Log),在何处引用该函数

若将Math.cpp文件中的Log函数注释掉进行编译,linker不会报错,因为linking过程中,并没有使用Log函数,所以原本的Log.cpp如和改变并不影响,因为Math.cpp根本没有使用Log函数,它被注释掉了

若将Math.cpp文件main函数中的Multiply与Log函数同时注释掉(仅需要注释掉含Multiply的语句,因为Log是被Multiply调用的),build项目,可能会得到一个linking error,可能的原因是:虽然文件中没有使用Multiply函数,技术上来讲可能在另一个文件中使用它,因此linker确实需要linking该函数。若有办法告诉编译器该函数——Multiply——仅仅会在此文件中使用,就可以去掉这种linking error(Multiply从未被调用,所以也永远不会调用Log),实现方法:Multiply前加上static,意味着这个Multiply函数只为该单元声明(也就是该例中的Math.cpp文件),此时linking将不会报错。

Math.cpp

#include 

void Log(const char* message);

static int Multiply(int a, int b)//linking报错时需要添加static
{
	Log("Multiply");
	return a * b;
}

int main()
{
	//std::cout << Multiply(5,8) << std::endl;//Multiply被注释掉
	std::cin.get();
}

一切回复正常后,再将Log函数的返回值类型发生改变。Log.cpp:

#include  

int Log(const char* message) //由void改为int
	std::cout << message << std::endl;
    return 0;//此句不加会产生C error
}

再次build项目,linking error:

 改变Log函数中的参数,Log.cpp:

#include  

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

 build项目将会再次报错,因为linker链接器要寻找的Log函数并没有另一个参数,仅有一个参数const char *,如果链接器找不到一摸一样的,就会产生linking error

 ②有重复符号(函数或变量有相同的名字和相同的签名)时,两个相同名称的函数具有相同的返回值和相同的参数。此时linker不明确那个link到那个。

Log.cpp函数中重复再写void Log内容后进行编译,编译器会报错:error C2084: 函数“void Log(const char *)”已有主体

即,函数中有重复符号时,编译器会报错,因为都在一个文件中,而不需要链接发生就可以判断。若将重复内容移至其他文件(例Math.cpp),此时编译文件不会出错,但是build项目将会报错,linking error:Log已被定义。链接器不知道该链接至那个函数。

发生可能:

文件复原可对项目成功build之后,新建header file,Log.h

Log.h中进行Log函数的定义,而Log.cpp中仅进行声明

 Math.cpp中的Multiply函数中调用log,Log.cpp的InitLog函数中也调用

//Log.cpp
#include  //include后,error消失
#include "Log.h"

void InitLog()
{
	Log("Initialized Log");
}

//Log.h
#pragma once

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

//Math.cpp
#include 
#include "Log.h"

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

int main()
{
	std::cout << Multiply(5,8) << std::endl;
	std::cin.get();
}

此时build整个项目,会报错,linking error

 此时三个文件中确实只有一个Log函数定义,报错原因:include语句,Math.cpp与Log.cpp中均进行了#include "Log.h",所以产生了两个Log函数的定义。

        解决方法:①将log函数标记为static. 这时该Log函数链接时只发生在内部,即Log函数被include进Math.cpp与Log.cpp时,只会对该文件内部有效。

static void Log(const char* message)

此时build项目成功

                        ②将Log函数标记为inline. inline的意思是将函数本身拿过来,而不是调用

inline void Log(const char* message) 

此时build项目成功

                        ③将定义移动至一个翻译单元中(报错原因是这个Log函数被包含在两个翻译单元中),可以把Log函数的定义移动至第三个翻译单元,或者把Log函数定义放到现有的一个翻译单元中。(将其放入Log.cpp,Log.h函数中仅留声明)

//Log.cpp
#include  //include后,error消失
#include "Log.h"

void InitLog()
{
	Log("Initialized Log");
}

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

//Log.h
#pragma once

void Log(const char* message); 


//Math.cpp
#include 
#include "Log.h"

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

头文件中不放函数的定义,仅包含声明就可以。只声明,不定义。

07

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/883301.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号