C++是如何工作的?

news2025/4/6 16:17:59

首先来看一个最基本的C++程序段。

#include <iostream>

int main()
{
	std::cout << "HelloWorld" << std::endl;
	std::cin.get();
}

第一行  #include  的含义是预处理的意思,这条语句的作用是将一个名为iostream的文件拷贝到源代码中这个文件通常被称为“头文件”,之所以要拷贝这个文件,是因为后面要使用到的cout、cin就存在于这个文件中(其实如果不怕麻烦,直接把iostream中的内容手动拷贝到源文件中也可以实现同样的效果),注意:在源代码实际编译之前,这段预处理代码就被执行了。

接下来看main函数,main函数的返回值类型是int,main函数比较特殊,不强制要求写明返回值,这里默认的返回值是0,当然也可以写明返回值,但是这样做没什么意义。

main函数体中,实现了输入(cin)和输出(cout)两个功能,"<<"符号看上去像左移运算符,实际上它被称为“重载运算符”,可以被理解为是一个函数。

代码编写完毕后,编译器会将代码编译成机器能够识别的机器码,可以在VS studio中进行编译选项配置,X64表明编译代码的目标平台,如果想修改运行平台,可以在该栏目中配置,另外解决方案配置中有Debug和release两种选项,在Debug模式下,程序的编译、运行速度会比release慢一些,因为release模式下会对程序进行一些性能方面的优化,而Debug模式则会默认关闭这些优化,但是关闭优化的好处就是我们可以调试代码。

854dc7cc37c7416bb28bde47cff39cef.png

项目中的每个CPP文件都会被编译,但是头文件不会被编译,因为头文件的内容在预处理时就被包含进来了,源文件被编译时,包含进来的头文件也一起被编译了。每个cpp文件都被编译成了一个目标文件,如果使用的是vs编辑器,生成文件的后缀是.obj,有了这些目标文件以后,需要把这些文件合并成一个可执行文件,此时就需要使用链接了,链接就是将所有的obj文件粘在一起,合并成一个exe文件。下面看这个例子,我新建了一个名为main.cpp的源文件,为了让程序看起来简洁一些,我把打印功能单独封装到一个Log函数中,为了防止程序报错,我在源文件main.cpp中声明一下Log函数,虽然我没有真正地实现Log函数,但是下面这段代码可以正常通过编译!

void Log(const char* message);
int main() {
	Log("Hello world");
}

但是却无法正常运行!

08b62676d189438caf6ec7b717aeadcc.png

这是因为在编译阶段,编译器只关心当前的源代码是否符合规范,是否包含词法错误、语法错误、静态语义错误,而不关心当前源文件与其他源文件的关联关系是否正确,这就是为什么虽然没有实现Log函数,但是main.cpp文件却通过了编译,因为编译器发现虽然我们使用了一个“陌生”的函数Log,但我们提前声明了Log函数,这符合C++的规范,因此编译放行,生成了main.obj文件,但在执行阶段,情况就复杂了一些,程序需要被真正地执行,但是程序此时发现Log函数没有函数体,因此就报错了,下面我们简单实现一下Log函数,我再新建一个源文件log.cpp,

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

此时程序就可以正常运行了!但是问题是,main.cpp是如何找到在log.cpp中定义的Log函数的位置呢?这时链接器就登场了!

编译器将每个cpp源文件单独编译程.obj文件,main.obj就包含了Log函数的声明信息,log.obj包含了Log的定义信息,

0e2b5c80e94e4c449543c3054a47032a.png

运行时,链接器就将这两个obj合并成一个.ilk文件,因此程序运行时就可以准确定位到声明函数的具体位置了!

9c72b6bede0d4a568a6a63a34764233a.png

下面详细介绍一下编译器和链接器的工作原理。

C++编译器

我们编写的代码实际上就是一个普通的文本文件,C++编译器需要做的就是将文本文件转换成一种被称为目标文件的中间格式,这些目标文件随后被传递给链接器,链接器会完成所有的链接工作。

在生成目标文件时会执行多个步骤,首先需要预处理代码,预处理完毕后,会进入词法分析、语法分析,将英语化的源代码整理成C++编译器可以理解的形式,会生成一个被称为抽象语法树的东西,编译器的最终任务是将所有代码转换为常量数据或指令。

首先在C++中,没有文件的概念(这与java语言有很大不同),文件只是向编译器提供源代码的一种方式,简单来说,如果我们创建的文件后缀名为.cpp文件,C++编译器会默认地将其视为C++文件进行编译,但是如果我们创建的文件名后缀为.happy(我自己乱写的一个后缀名),只要我告诉编译器这是一个C++文件,那么编译器依然会按照C++的规范去编译它,比如:

b794b0938a474d439527fc03e7d7f95c.png

然后在控制台这样去编译执行它,依然可以执行成功!(但是平时工作中最好不要这样做)

PS D:\cproject\C_Study\demo1> g++ -x c++ -o happy .\demo.happy
PS D:\cproject\C_Study\demo1> .\happy.exe
happy

先来看看编译的第一阶段——预处理,常用的预处理指令包括include、define、if\ifdef、pragma,先来看include指令,这个指令很简单,预处理器会将include包含的文件打开、读取全部内容,并将其粘贴到文件中,就是简单的粘贴复制!用一个简单的例子看一下:

我在main.cpp中写了如下代码:

int add(int a,int b) {
	return a + b;
#include "kuohao.h"

然后再kuohao.h中写:

}

看起来很古怪吧?kuohao.h头文件中只要一个右括号,但是没关系,我们的main.cpp依然可以正确编译并运行!因为 #include "kuohao.h"这段代码就相当于把kuohao.h中的内容原封不动地粘贴到了指定位置。

我们还可以让编译器输出一个文件,该文件包含所有预处理器的处理结果,我们可以在属性页中修改选项,将“预处理到文件”修改为“是”,

5f062087203c4177a1b4db55045f70ae.png

再次编译 main.cpp,可以看到生成了:

2481ea2807c14c81811d24697af87b7e.png

这个文件就是预处理后的C++代码,打开这个文件可以看到:

#line 1 "D:\\cproject\\C_Study\\demo1\\main.cpp"
int add(int a,int b) {
	return a + b;
#line 1 "D:\\cproject\\C_Study\\demo1\\kuohao.h"
}
#line 4 "D:\\cproject\\C_Study\\demo1\\main.cpp"

观察完效果后,记得把选项重新设置为“否”,否则编译将不会生成obj文件了!!! 

 接下来我们打开生成obj文件观察一下:

fbb422b66e1d4fc7a5580eb8b8fe6f7c.png

 全部是二进制文件,无法看懂,我们可以在vs设置一下,将“汇编程序输出”设置为如图所示:

655cec97668d46f0a5f4e2c4a967bc23.png

 输出目录里会生成asm汇编代码文件:

8a700ef05ef845e5b360fa27248cdbdd.png

 打开这个文件,就能看到相应的汇编代码:

2486b088ed824377a78a5bbe9072cce1.png

 最下面的"add@YAHHH@Z"这段代码,代表的是函数签名,链接器就靠函数签名去寻找函数的。

C++链接器

每个文件都被编译成一个obj文件,这些obj文件无法相互联系和作用,如果obj文件之间存在引用关系,就需要进行一个被称为链接的过程,链接的主要任务是找到每个符号和函数的位置,并将它们链接到一起,即使源文件中都没有使用其他文件,链接器也需要确定main函数的位置,比如如下代码:

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

这段代码可以正常编译,但是在运行时就会发生如下链接错误!提示程序没有“main函数”,意味着程序没有入口点。 

d879c5b1f4f343098280981ca4412a57.png

 默认情况下,每个exe程序的入口点都是main函数,但是实际上,我们可以指定一个程序的入口点,这可以在属性页中设置,因此C++程序的入口点不必一定是main。e4798624e6ec47d2bfc629825d98dd3f.png

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在 Windows WSL 上部署 Ollama 和大语言模型:从镜像冗余问题看 Docker 最佳实践20241208

&#x1f6e0;️ 在 Windows WSL 上部署 Ollama 和大语言模型&#xff1a;从镜像冗余问题看 Docker 最佳实践 ⭐ 引言 随着大语言模型&#xff08;LLM&#xff09;和人工智能技术的迅猛发展&#xff0c;开发者们越来越多地尝试在本地环境中部署模型进行实验。 但部署过程中常…

2、开发环境优化与创建第一个插件程序

一、创建测试用例二、vscode优化2.1 修改默认终端为普通cmd2.2 配置一键编译&&运行&&监视一、创建测试用例 使用命令yo code生成一个测试用例,选择或输入下面的内容。2. 命令的最后会提示是否使用vscode打开,选择打开就行。 3. 在当前目录下会产生helloworld…

公有云和私有云的区别

目录 ​1、公有云&#xff08;PublicClouds&#xff09; ​2、私有云&#xff08;PrivateClouds&#xff09; ​2.1 私有云又分为两种 3、混合云&#xff08;hybrid cloud&#xff09; ​3.1 公有云和私有云的区别 ​3.2 选择公有云或者是私有云 4 政务云&#xff08;Go…

TortoiseGit的下载、安装和配置

一、TortoiseGit的简介 tortoiseGit是一个开放的git版本控制系统的源客户端&#xff0c;支持Winxp/vista/win7.该软件功能和git一样 不同的是&#xff1a;git是命令行操作模式&#xff0c;tortoiseGit界面化操作模式&#xff0c;不用记git相关命令就可以直接操作&#xff0c;读…

jmeter调整字号无法生效?

调整之前如上图&#xff0c;字体非常小&#xff0c;哪怕我设置的字号是48 查阅了资料&#xff0c;试了几次&#xff0c;解决办法如下&#xff1a; 用编辑器打开jmeter.bat 在echo off的下一行添加以下代码 set JVM_ARGS%JVM_ARGS% -Dswing.plaf.metal.controlFontDialog-20…

【实操GPT-SoVits】声音克隆模型图文版教程

项目github地址&#xff1a;https://github.com/RVC-Boss/GPT-SoVITS.git官方教程&#xff1a;https://www.yuque.com/baicaigongchang1145haoyuangong/ib3g1e/tkemqe8vzhadfpeu本文旨在迅速实操GPT-SoVits项目&#xff0c;不阐述技术原理&#xff08;后期如果有时间研究&#…

【Web】2023安洵杯第六届网络安全挑战赛 WP

目录 Whats my name easy_unserialize signal Swagger docs 赛题链接&#xff1a;GitHub - D0g3-Lab/i-SOON_CTF_2023: 2023 第六届安洵杯 题目环境/源码 Whats my name 第一段正则用于匹配以 include 结尾的字符串&#xff0c;并且在 include 之前&#xff0c;可以有任…

大模型简单实践

大模型简单实践 最近参加了Datawhale AI冬令营&#xff08;第一期&#xff09;的活动 网站链接 手把手教学&#xff0c;借助Chat-嬛嬛 搭上讯飞星辰MaaS平台&#xff0c;快速训练处一个可以自由聊天的对话机器人。

Linux的基本功能和命令

Linux的基本功能和命令 切换目录 pwd 查询当前目录地址 cd /xxx/xxx 转到目录 cd …/ 回到上一级目录 cd ./ 当前目录 创建、删除文件/文件夹 创建文件\文件夹 touch filename 创建空文件mkdir 创建目录 mkdir -p 目标目录存在也不报错mkdir -p xxx/xxx 递归创建目录…

LLC谐振变换器的工作模态分析

概述 LLC谐振变换器在传统串联LC和并联LC谐振变换器的基础之上进行改进&#xff0c;既有LC串联谐振变换器谐振电容所起到的隔直作用和谐振网络电流随负载轻重而变化&#xff0c;轻载时效率较高的优点。同时又有LC并联谐振变化器可以在空载条件下&#xff0c;对滤波电容的电流脉…

Goby AI 2.0 自动化编写 EXP | Mitel MiCollab 企业协作平台 npm-pwg 任意文件读取漏洞(CVE-2024-41713)

漏洞名称&#xff1a;Mitel MiCollab 企业协作平台 npm-pwg 任意文件读取漏洞(CVE-2024-41713) English Name&#xff1a;Mitel MiCollab /npm-pwg File Read Vulnerability (CVE-2024-41713) CVSS core: 6.8 漏洞描述&#xff1a; Mitel MiCollab 是加拿大 Mitel 公司推出…

视频安防监控平台:Liveweb视频监控管理云平台方案

LiveWeb是深圳市好游科技有限公司开发的一套综合视频汇聚管理平台&#xff0c;可提供多协议&#xff08;RTSP/RTMP/GB28181/海康Ehome/大华&#xff0c;海康SDK等&#xff09;的视频设备接入&#xff0c;支持GB/T28181上下级联&#xff0c;RTSP\RTMP转GB/T28181&#xff0c;云台…

ip地址暴露了怎么办?手机怎样改ip地址以保障安全

在数字化时代,IP地址作为我们连接互联网的“身份证”,其安全性至关重要。然而,有时我们的IP地址可能会因各种原因暴露,从而引发隐私泄露、网络攻击等风险。本文将为您详细解析IP地址暴露后的应对措施,特别是针对手机用户,提供实用的更改IP地址方法,帮助您有效保障网络安…

组合分支预测

前言 这篇文章讨论了几种分支预测的实现方式。具体内容如下&#xff1a; 内容 introduction 这篇文章只考虑预测分支跳转方向&#xff0c;不讨论跳转的目标地址。 Bimodal Branch Prediction 分支行为的特点&#xff1a;大多数程序中的分支指令并不是随机的&#xff0c;通…

爬虫基础之代理的基本原理

在做爬虫的过程中经常会遇到一种情况&#xff0c;就是爬虫最初是正常运行、正常抓取数据的&#xff0c;一切看起来都是那么美好&#xff0c;然而一杯茶的工夫就出现了错误&#xff0c;例如 403 Forbidden&#xff0c;这时打开网页一看&#xff0c;可能会看到“您的IP访问频率太…

数据结构——对顶堆

对顶堆 由一个大根堆和一个小根堆组成&#xff0c;小根堆里面的数永远比大根堆里面的数要大 用途&#xff1a;用于动态维护区间内第k大的数&#xff0c;要比线段树和动态平衡树写起来更简单 比如说我们要维护第k大的数&#xff0c;那么我们肯定是将前k大的数放进小根堆&#…

设计模式之原型模式:深入浅出讲解对象克隆

~犬&#x1f4f0;余~ “我欲贱而贵&#xff0c;愚而智&#xff0c;贫而富&#xff0c;可乎&#xff1f; 曰&#xff1a;其唯学乎” 原型模式概述 在我们的日常生活中&#xff0c;经常会遇到"复制"这样的场景。比如我们在准备文件时&#xff0c;常常会复印一份原件&a…

Elasticsearch Serverless 中的数据流自动分片

作者&#xff1a;来自 Elastic Andrei Dan 在 Elastic Cloud Serverless 中&#xff0c;我们根据索引负载自动为数据流配置最佳分片数量&#xff0c;从而使用户无需摆弄分片。 传统上&#xff0c;用户会更改数据流的分片配置&#xff0c;以处理各种工作负载并充分利用可用资源。…

【Golang】Go语言编程思想(六):Channel,第四节,Select

使用 Select 如果此时我们有多个 channel&#xff0c;我们想从多个 channel 接收数据&#xff0c;谁来的快先输出谁&#xff0c;此时应该怎么做呢&#xff1f;答案是使用 select&#xff1a; package mainimport "fmt"func main() {var c1, c2 chan int // c1 and …

MindSearch深度解析实践

任务要求&#xff1a;在 官方的MindSearch页面 复制Spaces应用到自己的Spaces下&#xff0c;Space 名称中需要包含 MindSearch 关键词&#xff0c;请在必要的步骤以及成功的对话测试结果当中 1.在github codespace中配置环境 conda create -n mindsearch python3.10 -y conda…