欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > IT业 > 【C++】How the C++ Linker Works

【C++】How the C++ Linker Works

2025/4/3 22:03:53 来源:https://blog.csdn.net/qq_45951891/article/details/143312436  浏览:    关键词:【C++】How the C++ Linker Works
  • 如果是编译阶段的error,报错代码会以C开头(compiling),比如语句结束少写了分号
  • 如果是链接阶段的error,报错代码会以LNK开头(linking),比如缺少main函数

为什么缺少main函数就报错了呢?

Linker01

我们打开设置,可以看到配置类型是Application(.exe),而每个.exe文件必须有 entry point(入口点)

Linker02

我们还可以指定一个自定义的入口点,入口点不一定是main函数(通常是),只要有一个入口点就行了


让我们来看具体的例子

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

此时程序缺少main函数,编译(Compile)不会报错,生成(Build)会报错

#include<iostream>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();
}

我们将main函数加上,Link阶段就不会出问题了

我们可以把Log函数单独放到一个文件中(Log.cpp),这样可以降低程序的耦合度

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

此时直接编译会有compile error (error C3861: “Log”: 找不到标识符)

我们需要对Log函数进行声明

#include<iostream>void Log(const char* message);//函数声明int Multiply(int a, int b)
{Log("Multiply");return a * b;
}

我们再进行Compile,没有问题,那我们接着Build,又出现问题了

1>E:\SavingProject_C++\HelloWorld\HelloWorld\Log.cpp(2,7): error C2039: "cout": 不是 "std" 的成员
1>    E:\SavingProject_C++\HelloWorld\HelloWorld\predefined C++ types (compiler internal)(347,11):
1>    参见“std”的声明
1>E:\SavingProject_C++\HelloWorld\HelloWorld\Log.cpp(2,7): error C2065: “cout”: 未声明的标识符
1>E:\SavingProject_C++\HelloWorld\HelloWorld\Log.cpp(2,31): error C2039: "endl": 不是 "std" 的成员
1>    E:\SavingProject_C++\HelloWorld\HelloWorld\predefined C++ types (compiler internal)(347,11):
1>    参见“std”的声明
1>E:\SavingProject_C++\HelloWorld\HelloWorld\Log.cpp(2,31): error C2065: “endl”: 未声明的标识符

我们应该在Log.cpp中加上#include<iostream>

这样就没问题啦


我们再来看一个可能会出现的 linking error——unresolved external symbol(无法解析的外部符号)

It happens when the Linker can’t find something that it needs.

在Log.cpp中将Log函数名加一个r,变成Logr

Compile不会出现报错,他相信在某处有一个Log函数,而要找到它就是Link的事情了

因为我们将Log变成了Logr,自然就找不到了,Build自然就会报错:unresolved external symbol(无法解析的外部符号)

error LNK2019: 无法解析的外部符号 "void __cdecl Log(char const *)" (?Log@@YAXPEBD@Z),函数 "int __cdecl Multiply(int,int)" (?Multiply@@YAHHH@Z) 中引用了该符号

如果我们将Log("Multiply");这一行代码注释了,就不会报错了

这是因为我们没有调用过Log函数,链接器不需要去链接


如果我们不将那一行注释,而是将std::cout << Multiply(5, 8) << std::endl;这一行注释

即不调用Multiply函数

但是报错了,这是为什么呢?

虽然我们没有在这个文件里调用Multiply函数,但技术上讲,我们可能在另一个文件中会使用它

所以链接器确实需要链接它


如果我们能告诉编译器,这个Multiply函数我只会在这个文件中使用它。

当然,我们可以去掉这种链接的必要性,因为Multiply从来不会被调用,也就从不需要Log

int Multiply(int a, int b)前加上static

这意味着Multiply函数只被声明在这个翻译单元中(也就是math.cpp)

此时我们再进行Build,不会报错;而把注释删去,仍然会报错


在这个例子中,我们改变的是函数名,会出现报错;

同样的,如果参数列表和返回值不一样,也会出现报错。

函数名一样,参数列表和返回值不一样,这就是一个新的函数

为什么可以这样?还有我们调用的时候怎么知道自己调用的是哪一个呢?

这就涉及到函数重载了,我们以后会讲


那如果有两个一模一样的函数呢?

Log.cpp

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

compiling error

函数“void Log(const char *)”已有主体(already has a body)

我们将一份移到Math.cpp中

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

linking error

1>Math.obj : error LNK2005: "void __cdecl Log(char const *)" (?Log@@YAXPEBD@Z) 已经在 Log.obj 中定义
1>E:\SavingProject_C++\HelloWorld\x64\Debug\HelloWorld.exe : fatal error LNK1169: 找到一个或多个多重定义的符号

链接器不知道它该链接哪一个Log函数,是Log.cpp中的还是Math.cpp中的?


看完以上这些你可能会觉得自己并不会犯这样的错误,那我们接着往下看

创建头文件Log.h 将Log.cpp此部分内容剪切到Log.h中

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

Log.cpp变成

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

此时编译:error C3861: “Log”: 找不到标识符

在Log.cpp中添加#include "Log.h"

在Math.cpp中删去函数声明,同样地添加#include "Log.h"

此时生成:

1>LINK : 没有找到 E:\SavingProject_C++\HelloWorld\x64\Debug\HelloWorld.exe 或上一个增量链接没有生成它;正在执行完全链接
1>Math.obj : error LNK2005: "void __cdecl Log(char const *)" (?Log@@YAXPEBD@Z) 已经在 Log.obj 中定义
1>E:\SavingProject_C++\HelloWorld\x64\Debug\HelloWorld.exe : fatal error LNK1169: 找到一个或多个多重定义的符号

实际上我们就定义了一个Log函数,它在Log.h中。那为什么会说我们重复定义了呢?

还记得#include的工作原理吗?

在How the C++ Compiler Works中我们是这样说的

You basically specify which file you want to include and then the pre-processor will open that file read all of its contents and just paste it into the file where you wrote your include statement.

你只需指定要包含的文件,然后预处理器将打开该文件,读取其所有内容,然后将其粘贴到你写include语句的文件中。

在Log.cpp和Math.cpp中都有#include "Log.h",也就都定义了Log函数

我们如何解决这个问题呢?

(1)标记Log函数为静态的(前面加static),这意味着在链接Log函数时,它只能是内部函数。这样Log.cpp和Math.cpp都会有自己版本的Log函数,它对任何其他的obj文件都不可见。

(2)前面加inline,它会获取我们实际的函数体并将函数调用替换为函数体

在这种情况下,InitLog函数实际上是这样的

void InitLog() {std::cout << "Initialized Log" << std::endl;
}

(3)将Log函数的定义放到Log.cpp中,而在Log.h中声明它

对于static和inline还不了解的同学可以先记这种,我们以后会对它们进行详细讲解

视频:https://www.youtube.com/watch?v=H4s55GgAg0I

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词