Cherno C++系列笔记1——P5~P7 C++工作原理、编译和链接器原理

Cherno C++系列笔记1——P5~P7 C++工作原理、编译和链接器原理,第1张

文章目录 1.P5 C++是如何工作的1.1.预处理1.2.编译和链接 2.P6 C++编译器是如何工作的3.P7 C++链接器是如何工作的3.1.链接器的工作3.2.静态函数staticlass="superseo">c避免找不到定义问题3.3.链接时由include导致的多重定义问题3.3.1.问题描述3.3.2.问题原因3.3.3.问题解决

1.P5 C++是如何工作的

参考:视频 笔记

1.1.预处理

在#符号之后的都是预处理语句,编译器收到源文件后会预先处理。之所以叫预处理语句,是因为在实际编译发生之前就被处理了。
 include的含义是需要找个一个文件,在这里指的是需要找到叫iostream的文件,然后将该文件的所有内容复制粘贴到现在的文件内,这些所包含的文件通常被成为“头文件”

1.2.编译和链接

项目中的每一个cpp文件都会被编译,但是头文件不会被编译,头文件的内容在预处理时包含到了cpp中。每一个cpp文件都被编译为object file(目标文件),如果使用VS生成的文件后缀为.obj。我们需要将这些文件合并成一个执行文件,链接(Link)会将所有的obj文件黏合在一起,合并成一个.exe文件。

如果是多文件工程,那么如果当前文件调用外部文件中定义的函数,那么需要有外部函数的声明,这样编译器就会信任我们知道有这样一个外部函数,编译就可以正常进行。这个函数到底在哪里,就是链接器负责的了。

2.P6 C++编译器是如何工作的

参考:视频 笔记

C++编译器只负责一件事,将文本文件(C++代码)转换成称为目标文件的中间格式。这些obj文件可以传递到链接,链接可以做它所有要链接的事情。编译器在生成这些obj时,所有的预处理器语句都会被先处理,接下来将进行记号化和解析,把C++代码整理成编译器能够真正理解和推理的格式。

我们提供给编译器的每个c++文件,编译器都将把文件变成翻译单元(一个cpp文件不一定要等于一个翻译单元),翻译单元会生成一个obj文件。

关于翻译单元的意思还不是很懂。 3.P7 C++链接器是如何工作的

参考:视频 笔记

3.1.链接器的工作

一旦我们编译好源文件,我们需要通过一个叫做链接的过程。链接的主要作用是找到每个符号和函数所在的地方,并把它们链接起来。多个翻译单元之间并不互通,我们需要一种方法把这些文件连接起来成一个项目。即使只有一个翻译单元,也需要将main函数连接起来。

3.2.静态函数static避免找不到定义问题

如下所示,只有一个main.cpp文件,Log函数声明了,但是并没有在外部进行定义。

如果我们不调用Multiply函数,重新build仍然会报错如下图。因为虽然在Math.cpp我们不用Multiply函数,但是从技术上讲,我们有可能在另一个文件中用到这个函数,所以链接器需要链接到它。

如果我们告诉编译器Multiply函数只会在这个文件中使用,就可以去掉链接的必要性。我们可以在Multiply函数前写个“static”静态这个词,意味这Multiply函数只被声明在这个翻译单元(Math.cpp)中,再次build会发现没有任何链接错误。

3.3.链接时由include导致的多重定义问题 3.3.1.问题描述

考虑如下的程序结构,有log.hlog.cppmain.cpp三个文件,内容如下:

log.h文件:
#param once

void log(const char* msg)
{
	std::cout << msg << std::endl;
}
log.cpp文件:
#include
#include"log.h"

void Initlog() {
	log("Initialized Log");
}
main.cpp文件:
#include
#include"log.h"

int main()
{
	log("Hello Main Cpp");
	std::cin.get();
}

这个时候如果编译整个项目,会得到一个链接多重定义的错误。

3.3.2.问题原因

实际上只有Log.h定义了Log函数,为什么会抱怨多重符号呢?有两个原因:

原因1:因为我们在Log.cpp,main.cpp中都使用了#include"Log.h",这样就相当于直接把log.h文件中的内容复制拷贝到了相应的文件中,也就是上面的源文件(只有源文件会被编译)变成了如下形式: log.cpp文件:
#include

void log(const char* msg)
{
	std::cout << msg << std::endl;
}

void Initlog() {
	log("Initialized Log");
}
main.cpp文件:
#include

void log(const char* msg)
{
	std::cout << msg << std::endl;
}

int main()
{
	log("Hello Main Cpp");
	std::cin.get();
}
原因2:所有的源文件都会被编译和链接,即使这个源文件中的函数没有被调用。因为log.cpp文件中定义的InitLog函数并没有被外部函数调用,但是链接器认为这个函数有可能会被外部调用,因此仍然会链接它。这样就导致main.cpplog.cpp中的log这个函数都会被链接,导致多重链接的错误。 3.3.3.问题解决

方法1——static静态函数限制在本文件中链接
我们可以将Log函数标记为静态的,这意味在链接Log函数时,Log函数只能是内部函数。这样在Log.cpp,Main.cpp中的Log函数只能是文件的内部函数,Log.cppMain.cpp文件都有自己版本的Log函数,对任何其它的obj文件不可见。重新build就不会报错。

方法2——内联函数化函数调用为本地语句
在函数名前面加上inline关键字限定,这样在调用这个函数的地方将会直接替换为这个函数的实体(类似include的感觉),而不会执行函数的调用(就不需链接到这个函数定义的地方)

log.h文件:

#param once

inline void log(const char* msg)
{
	std::cout << msg << std::endl;
}
方法3——把函数定义放到.cpp中实现只有一个地方可以链接(最规范的用法)
如下所示,这样log函数的实体只在一个地方存在,就没有链接时的多重定义问题。

log.h文件:

#param once

void log(const char* msg);

log.cpp文件:

#include
#include"log.h"

void log(const char* msg)
{
	std::cout << msg << std::endl;
}

void Initlog() {
	log("Initialized Log");
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/990572.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存