【C++】How the C++ Compiler Works

Firstly it needs to pre-process our code which means that any pre-processor statements get evaluated and once our code has been pre-processed we move on to more or less tokenizing(记号化) and parsing(解析) and basically sorting out(整理) this English C++ language into a format that the complier can actually understand and reason with.

首先,它需要预处理我们的代码,这意味着任何预处理语句都会被处理,一旦我们的代码被预处理,我们就会继续进行或多或少的记号化和解析,并基本整理成英文的c++语言,使其成为编译器可以真正理解和推理的格式。

This basically results in something called an abstract syntax tree(抽象语法树) being created which is basically a representation(表示) of our code but as an abstract syntax tree.The compiler’s job at the end of the day is to convert all of our code into either constant data or instructions. Once the compiler has created this abstract syntax tree it can begin actually generating code,now this code is going to be the actual machine code that our CPU will execute.We also wind up with various other data such as a place to store all of our constant variables and that’s essentially all the compiler does.

这基本上会导致创建一个抽象语法树,它基本上是我们代码的一个表示,但是是作为一个抽象语法树。最终,编译器的工作是将所有代码转换为常量数据或指令。一旦编译器创建了这个抽象语法树,它就可以开始实际生成代码,现在这些代码将成为我们的CPU将要执行的实际机器码。我们还会得到各种其他数据,比如存储所有常量变量的地方,这就是编译器所做的一切。


What the compiler has done is has generated objects files for each of our C++ files for each of our translation units.Now every CPP file that our project contains that we actually tell the compiler: Hey,compile this CPP file.Every single one of those files will result in an object file.These CPP files are things called translation units essentially.

编译器所做的是为每个翻译单元的每个c++文件生成对象文件。现在我们的项目包含的每个CPP文件我们实际上都会告诉编译器:嘿,编译这个CPP文件。这些文件中的每一个都会产生一个目标文件。这些CPP文件本质上叫做翻译单元。

**You have to realize that C++ doesn’t care about files.**Files are not something that exists in C++.

你必须意识到c++并不关心文件。文件不存在于c++中。

But in Java,your class name has to be tied to your file name and your folder hierarchy has to be tied to your package .(在Java中,类名必须和你的文件名称一样,你的文件夹层次需要和package一样)

这也让我想起了“Linux一切皆文件”

In C++,a file is just a way to feed(提供) the compiler with source code.You’re responsible for telling the compiler what kind of file type this is and how the compiler should treat that.Now of course if you create a file with the extension CPP the compiler is going to treat that as a C++ file.Similarly if you make a file with the extension “.c” or “.h”,the compiler is going to treat the .c file like a C file not a C++ file.And it’s going to treat .h file like a header file.

在c++中,文件只是向编译器提供源代码的一种方式。你负责告诉编译器这是哪种文件类型以及编译器应该如何处理它。当然,如果你创建一个扩展名为CPP的文件,编译器会将其视为c++文件。类似地,如果你创建一个扩展名为“.c”或“.h”的文件,编译器会将.C文件视为C文件,而不是c++文件;它会将.h文件视为头文件。

These are basically just default conventions that are in place you can override any of them.If you don’t tell it how to deal with it,I could go around making .cherno files and telling the compiler: Hey,this file is a C++ file,please compile it like a C++ file.So just remember files have no meaning.

这些基本上都是默认的约定,你可以重写它们中的任何一个。如果你不告诉它如何处理,也可以去制作.cherno文件,然后告诉编译器:嘿,这个文件是一个c++文件,请像编译c++文件一样编译它。所以要记住文件没有意义。


So that being said every C++ file that we feed into the compiler and we tell it this is a C++ file please compile it,it will compile it as a translation unit and a translation unit will result in an object file.

也就是说,我们输入编译器的每个c++文件告诉它这是一个c++文件,请编译它,它会把它编译成一个翻译单元,一个翻译单元会产生一个目标文件。

It’s actually quite common to sometimes include CPP files in other CPP files and create basically one big CPP file with a lot of files in it.If you do something like that and then you only compile the one CPP file you’re going to basically result in one translation unit and thus one object file.

实际上,有时将CPP文件包含在其他CPP文件中并创建一个包含许多文件的大CPP文件是很常见的。如果你那样做了然后只编译一个CPP文件,你就会得到一个翻译单元,自然也就得到一个目标文件。

So that’s why there’s that terminology split(这就是为什么术语上有区别) between what a translation unit is and what a CPP file actually is because a CPP file doesn’t necessarily have to equal a translation unit.However if you just make a project with individual CPP files and you never include them in each other then yes every CPP file will be a translation unit and every CPP file will generate an object file.

因为CPP文件并不一定等于翻译单元。但是,如果您只是使用单个CPP文件创建一个项目,并且您从未将它们包含在彼此中,那么每个CPP文件将是一个翻译单元,每个CPP文件将生成一个目标文件。

How does “hash include”(#include) work?

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语句的文件中。

让我们来证明这一点

新建一个 Math.cpp

int Multiply(int a, int b)
{
	int result = a * b;
	return result;
}

编译成功!

新建一个 EndBrace.h

}

将Math.cpp改为

int Multiply(int a, int b)
{
	int result = a * b;
	return result;
#include "EndBrace.h"

编译成功!

实际上有一种方法我们可以告诉编译器,输出一个文件,其中包含所有的结果,包括所有的预处理器评估情况。这样有助于我们更好的理解预处理,我们开始之前要对VS的设置进行更改

Compiler01

如图所示,将否改为是,此项改动产生的影响如红框所示,所以我们使用完记得改回去哦

然后我们对Math.cpp进行编译,会发现在Debug文件夹下生成了Math.i文件,打开如下:

#line 1 "E:\\SavingProject_C++\\HelloWorld\\HelloWorld\\Math.cpp"
int Multiply(int a, int b)
{
	int result = a * b;
	return result;
#line 1 "E:\\SavingProject_C++\\HelloWorld\\HelloWorld\\EndBrace.h"
}
#line 6 "E:\\SavingProject_C++\\HelloWorld\\HelloWorld\\Math.cpp"

我们接下来会继续对Math.cpp进行更改并编译,查看Math.i的内容

Math.cpp

#define INTEGER int

INTEGER Multiply(int a, int b)
{
	INTEGER result = a * b;
	return result;
}

Math.i

#line 1 "E:\\SavingProject_C++\\HelloWorld\\HelloWorld\\Math.cpp"


int Multiply(int a, int b)
{
	int result = a * b;
	return result;
}

Math.cpp

#define INTEGER Cherno

INTEGER Multiply(int a, int b)
{
	INTEGER result = a * b;
	return result;
}

Math.i

#line 1 "E:\\SavingProject_C++\\HelloWorld\\HelloWorld\\Math.cpp"


Cherno Multiply(int a, int b)
{
	Cherno result = a * b;
	return result;
}

Math.cpp

#if 1
int Multiply(int a, int b)
{
	int result = a * b;
	return result;
}
#endif

Math.i

#line 1 "E:\\SavingProject_C++\\HelloWorld\\HelloWorld\\Math.cpp"

int Multiply(int a, int b)
{
	int result = a * b;
	return result;
}
#line 8 "E:\\SavingProject_C++\\HelloWorld\\HelloWorld\\Math.cpp"

Math.cpp

#if 0
int Multiply(int a, int b)
{
	int result = a * b;
	return result;
}
#endif

Math.i

#line 1 "E:\\SavingProject_C++\\HelloWorld\\HelloWorld\\Math.cpp"






#line 8 "E:\\SavingProject_C++\\HelloWorld\\HelloWorld\\Math.cpp"

Math.cpp

#include<iostream>

int Multiply(int a, int b)
{
	int result = a * b;
	return result;
}

Math.i

你可以看到里面有很多很多内容,拉到底部就是我们自己的函数

到这里可以把设置改回去啦

What’s actually inside our obj file?

以下内容理解即可

如果用文本编辑器打开会发现 It’s binary 二进制格式

It’s actually the machine code that our CPU will run when we call this multiply function

它实际上是我们的CPU在调用乘法函数时会运行的机器码

让我们再进行一些设置让它变得readable
Compiler02

编译后就能在输出目录下(Debug)看到Math.asm文件

Compiler03

有一堆汇编命令,这些是CPU将要执行的实际指令

Compiler04

我们的乘法操作实际发生在这

Compiler05

We load the a variable into our eax register and then we perform an IML instruction which is a multiplication on the b variable and that a variable we’re then storing the result of that in a variable called result and then moving it back into eax to return it.The reason this kind of double move happens is because I actually made a variable called result and then returned it instead of just returning a*b.

我们将a变量加载到eax寄存器中然后我们执行一个IML指令对b变量进行乘法运算然后我们将结果存储在一个名为result的变量中然后将其移回eax以返回它。发生这种来回移动是因为我实际上创建了一个名为result的变量然后返回它而不是返回a*b

That’s why we get this moving eax into result and then moving result into eax which is completely redundant.This is another a great example of why if you set your compiler not to optimize you’re going to wind up with slow code cause it’s doing extra stuff like this for no reason.

这就是为什么我们把eax移到result,然后又把result移到eax是完全多余的。同时也是一个很好的例子来说明,如果你设置编译器为不优化,你会得到缓慢的代码,因为它会无缘无故地做一些额外的事情。

将代码更改并编译

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

操作就会变少

Compiler06

We’re just doing imal on b and eax is actually going to contain our return value.

我们只是对b进行了imal运算而eax实际上包含了我们的返回值。

All of this may like a lot of code because we’re actually compiling in debug which doesn’t do any optimization and does extra things to make sure that our code is as verbose as possible and as easy to debug as possible.

这些看起来有很多代码,因为我们实际上是在调试中编译的,它没有做任何优化,而是做了额外的事情来确保我们的代码尽可能冗长,尽可能容易调试。

我们再更改一下设置

优化:

Compiler07

代码生成:

Compiler08

然后进行编译

That looks a lot smaller.

Compiler09

We’ve basically just got our variables being loaded into a register and the multiplication.

我们基本上只是把变量加载到寄存器和乘法运算中。

将代码更改并编译

int Multiply()
{
	return 5 * 2;
}

What it’s done is actually really simple.

Compiler10

It’s simply moved 10 into our eax register which is the register that will actually store our return value in.So if we take a look at our code again,it’s basically just simplified our 5*2 to be 10.Because of course there’s no need to do something like 5*2 two constant values at runtime.This is something called constant folding where anything is constant can be worked out at compile time.

它只是把10移到eax寄存器中,eax是实际存储返回值的寄存器。所以如果我们再看一下我们的代码,它基本上只是把5*2简化为10。因为当然没有必要在运行时做像5*2这样的事情。这就是所谓的常数折叠,任何常数都可以在编译时计算出来。

involving another function

const char* Log(const char* message) {
	return message;
}

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

可以看到它把我们的message指针移动到eax

Compiler11

Before we do multiplication by using the imal we actually call this Log function.You might be wondering why this Log function is decorated by what seems like random characters and at signs.

在用imal做乘法之前我们先调用了Log函数。您可能想知道为什么这个Log函数用看似随机的字符和@符号来装饰。

This is actually the function signature.This needs to uniquely define your function.

这实际上是函数签名。用来唯一地定义您的函数。

Essentially when we have multiple objs and our functions are defined in multiple objs,it’s going to be the linker’s job to link all of them together.And the way it’s going to do what is looking up this function signature.All you need to know here is that we’re calling this Log function.When you call a function it will generate a call instruction.

本质上,当我们有多个obj并且我们的函数在多个obj中定义时,链接器的工作是把它们链接在一起。它要做的就是查找这个函数签名。你只需要知道我们调用了Log函数。当你调用一个函数时,它会生成一个调用指令。

In this case it might be a little bit stupid because you can see that we’re simply calling Log even not storing the return value basically this could be optimized quite a bit.

在这种情况下,它可能有点愚蠢。因为你可以看到我们只是调用Log,甚至不存储返回值。实际上这可以被优化很多。

将优化设置改为“最大优化(优选速度) (/O2)”进行编译。就会发现call命令消失了,但笔者所用版本(Microsoft Visual Studio 2022社区版)并不会这样。

You should basically now understand the gist of how the compiler works.It will take our source files and output an object file which contains machine code and other constant data that we’ve defined.And we’ve got these object files we can link them into one executable which contains all of the machine code that we actually need to run.

现在您应该基本理解了编译器工作原理的要点。它将获取我们的源文件并输出一个目标文件,其中包含机器代码和我们定义的其他常量数据。我们有了这些目标文件就可以把它们链接到一个可执行文件里面,其中包含了我们实际需要运行的所有机器码。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/903116.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第2次CCF CSP认证真题解

1、相邻数对 题目链接&#xff1a;https://sim.csp.thusaac.com/contest/2/problem/0 本题和第1次认证的第1题“相反数”差不多&#xff0c;都是考察循环遍历比较和计数。 100分代码&#xff1a; #include <iostream> using namespace std; int main(int argc, char …

一款强大的开源OCR工具,支持90+语言识别

大家好&#xff0c;今天给大家分享一款功能强大的开源光学字符识别&#xff08;OCR&#xff09;工具Surya OCR&#xff0c;它基于先进的深度学习技术&#xff0c;提供了高效的字符识别能力&#xff0c;并支持多种语言的文本检测与识别。 项目介绍 核心功能 1.多语言支持 Sur…

破局:DLinear

1. Introduction (1) time series forecasting (TSF)&#xff1b; (2) 回顾 “ Transformer (Vaswani et al. 2017) ” 的各领域优秀表现&#xff1a; (3) IMS vs. DMS : → Consequently, IMS forecasting is preferable when there is a highly-accurate single-step fore…

量化交易打怪升级全攻略

上钟&#xff01; 继续分享量化干货~ 这次要唠的是Stat Arb的新作《Quant Roadmap》(中译名《量化交易路线图》)&#xff0c;为了方便&#xff0c;下文就称呼作者为“老S”&#xff0c;根据公开资料显示&#xff0c;他可是正儿八经的的量化研究员出身&#xff0c;在漂亮国头部对…

【electron8】electron实现“图片”的另存为

注&#xff1a;该列出的代码&#xff0c;都在文章内示例出 1. 另存为按钮事件&#xff1a; const saveAsHandler async () > {const { path, sessionId } recordInfoif(typeof message ! string) return;// 因为我的图片是加密的&#xff0c;所以我需要根据接口返回的路…

全国智能手机使用数据集-dta格式(包含2015、2017、2019三个版本)

数据简介&#xff1a;为推动经济社会高质量发展&#xff0c;缓解经济下行压力&#xff0c;中国加大推动5G基建、大数据中心等科技领域基础设施的建设和完善。数字技术深入各行各业&#xff0c;催生了新业态、新模式、新机会和新就业形式。智能手机作为劳动者使用数字技术的重要…

二叉树的存储方式和遍历方式

文章目录 二叉树的存储方式二叉树的遍历方式DFS--递归遍历DFS--迭代遍历BFS--层次遍历 LC102 二叉树的存储方式 链式存储&#xff08;指针&#xff09;或 顺序存储&#xff08;数组&#xff09; (1)链式存储&#xff1a;通过指针把分布在各个地址的节点串联一起。 (2)顺序存储…

docker上传离线镜像包到Artifactory

docker上传离线镜像包到Artifactory 原创 大阳 北京晓数神州科技有限公司 2024年10月25日 17:33 北京 随着docker官方源的封禁&#xff0c;最近国内资源也出现无法拉取的问题&#xff0c;Artifactory在生产环境中&#xff0c;很少挂外网代理去官方源拉取&#xff0c;小编提供…

大模型面试-Layer normalization篇

1. Layer Norm 的计算公式写一下&#xff1f; 2. RMS Norm 的计算公式写一下&#xff1f; 3. RMS Norm 相比于 Layer Norm 有什么特点&#xff1f; 4. Deep Norm 思路&#xff1f; 5. 写一下 Deep Norm 代码实现&#xff1f; 6.Deep Norm 有什么优点&#xff1f; 7.LN 在 LLMs …

每日一题之电话号码的字母组合

给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "23" 输出&#…

微信小程序学习实录11:精通表单数据绑定,构建高效用户界面

微信小程序中的表单数据绑定是一种非常实用的功能&#xff0c;它允许开发者将页面上的表单元素与数据进行关联&#xff0c;从而实现数据的双向绑定。这样做的好处是能够简化代码&#xff0c;提高开发效率&#xff0c;并且让数据管理变得更加直观。 一、基本概念 数据绑定&am…

Spring Cloud +UniApp智慧工地源码,智慧工地综合解决方案,建筑工程云平台源码

Spring Cloud UniApp智慧工地源码&#xff0c;智慧工地全套源代码包含&#xff1a;PC端大屏端移动端 智慧工地解决方案以工程建设现场管理需求为主线&#xff0c;以AI、物联网、BIM技术为手段&#xff0c;对施工现场进行立体化、全方位、全时段管理&#xff0c;实现规范施工管…

解决VMware虚拟机的字体过小问题

前言&#xff1a; &#xff08;1&#xff09;先装VMware VMware17Pro虚拟机安装教程(超详细)-CSDN博客 &#xff08;2&#xff09;通过清华等镜像网站安装好Ubuntu镜像&#xff0c;下面贴上链接 教程虚拟机配置我没有做&#xff0c;因为学校给了现成的虚拟机~~大家需要的自己…

数据结构之单链表——考研笔记

文章目录 一.单链表定义1.什么是单链表2.代码实现3.不带头结点的单链表4.带头结点的单链表 二.单链表插入删除1.按位序插入&#xff08;带头结点&#xff09;2.插入时不带头节点3.指定节点的后插操作4.指定节点的前插操作5.按位序删除&#xff08;带头结点&#xff09;6.删除指…

2024年【北京市安全员-A证】找解析及北京市安全员-A证考试试卷

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 北京市安全员-A证找解析考前必练&#xff01;安全生产模拟考试一点通每个月更新北京市安全员-A证考试试卷题目及答案&#xff01;多做几遍&#xff0c;其实通过北京市安全员-A证证考试很简单。 1、【多选题】《中华人…

保姆级教程 | 全流程免费:合并多份长宽不同的PDF成相同大小并进行瘦身

背景 由于老板需要&#xff0c;完成不同PDF文件&#xff08;a&#xff0c;b&#xff0c;c....&#xff09;合并&#xff0c;同时要求主文件&#xff08;A&#xff09;小于6M。合并过程中发现各个PDF大小&#xff08;长宽&#xff09;并不相同&#xff0c;造成合并后效果不好也…

网站安全问题都有哪些,分别详细说明

网站安全问题涉及多个方面&#xff0c;以下是一些常见的网站安全问题及其详细说明&#xff1a; 数据泄露 问题描述&#xff1a;数据泄露是指网站存储的用户敏感信息&#xff08;如用户名、密码、信用卡信息等&#xff09;被非法获取。黑客可能通过SQL注入、XSS攻击等手段窃取这…

Unity编辑器界面及其基础功能介绍

文章目录 Unity编辑器界面编辑器默认界面布局打开和关闭编辑界面自定义界面布局Unity资源商店Unity Assets Store什么是资源商店&#xff1f;资源商店中包含哪些东西&#xff1f;如何进行素材导入&#xff1f;Unity官网购买素材或插件导入方法非官网素材导入非官网插件导入 Sce…

【WRF数据准备】基于GEE下载静态地理数据-叶面积指数LAI及绿色植被率Fpar

【WRF数据准备】基于GEE下载静态地理数据 准备:WRF所需静态地理数据(Static geographical data)数据范围说明基于GEE下载叶面积指数及绿色植被率GEE数据集介绍数据下载:LAI(叶面积指数)和Fpar(绿色植被率)数据处理:基于Python处理为单波段LAI数据参考GEE的介绍可参见另…

基于Django+python的酒店客房入侵检测系统设计与实现

项目运行 需要先安装Python的相关依赖&#xff1a;pymysql&#xff0c;Django3.2.8&#xff0c;pillow 使用pip install 安装 第一步&#xff1a;创建数据库 第二步&#xff1a;执行SQL语句&#xff0c;.sql文件&#xff0c;运行该文件中的SQL语句 第三步&#xff1a;修改源…