【C++】编译

news2024/11/26 16:48:00

三、C++编译

前面给大家演示了如何从写C++代码到编译代码再到执行代码的全过程。这个过程中非常重要的编译环节,被我们一个按钮或者一个ctrl+F7快捷键就给带过了。其实这个环节非常重要,如果你非常了解这个环节,你开发源代码就会更加自信和清醒,而不是迷迷糊糊,摸棱两可……

下图直观描述了从源代码到计算机执行完毕的各个大环节:

内容实在多,图上没位置了,这里就继续说明上图。上图的1234是几个大步骤。1是我们的开发环境,你的代码就是在这个平台上书写、调试、编译、执行。。。等等

上图2是我们在1中开发的代码。这4行代码的功能就是把一个数字3存储到内存。但是A是我们人类能看懂的英文文字、数字3、一些符号,比如分号、小括号、花括号等等。那cpu可是看不懂这些呀,所以我们要把A编译成D2,因为cpu可以看懂D2,cpu就可以执行了D2了,执行完毕就可以完成我们人类的任务:写个3到内存。本部分重点讲3这个过程,就是如何编译的。

那我们再讲点题外的东西,以便我们对整体流程有一个认识。那就是后面的4,4说的就是cpu是如何执行D2的。我们说了D2是可执行二进制指令,也就是机器码(machine code),这些机器码先按照字节为单位加载到内存,就是E,当然E会很长很长,上图我就画了2个内存单位。加载完毕后就逐个单元地送到CPU。CPU只是一些电路(当然这个电路会及其复杂,cpu又称芯片,要不怎么芯片被卡脖子呢),它功能其实非常简单,它只会从内存读取数据、往内存中写入数据。当内存的这些指令数据送到CPU后,有用这些指令0101,就是高电平低电平的意思,那这些不同的特殊组合的高低电平流就会引起cpu中的晶体管打开或关闭,也就是引起不同的电路,不同的电路又会产生不同的输出结果,不同的输出结果指的是不同的高低电平的输出,也就是有序的0101序列的输出,而输出就是输出到内存。而内存上不同序列的0101又和人类认识的符号相对应。于是输出就又能映射成人类理解的东西,比如屏幕上的一行字,比如声卡输出的一段音频等等人类的东西。
具体到我们上图的例子,cpu输出的结果就是在内存的某个内存单元里面存了个3。那cpu具体是如何执行F的呢?我们看4.1,4.1中的H就是部分机器码,都是0101,我们看不懂,没关系,我先把这0101转化为16进制,就是J,J我们还看不懂,那我们把J在转化成K(这个过程叫优化,比如全部是0的就是空行的意思,也就是啥也不用做,所以可以删除了。关于优化环节我们后面还要单独拿出来演示),K还是看不懂,那我们把K再转化为L和M。L和M我们人类认识吧,这就是汇编语言,至少里面mov是move的意思,是我们人类的tocken,呵呵。。其中L的意思启动和结束的意思,仅仅是M这行代码是我们人类的任务:mov一个数字3到内存的后面表示的那个内存地址里面。这才是我们人类任务执行完毕的整个流程。

看似已经洋洋洒洒写了很多了,其实这个流程也是只是个大概。上图中每个箭头都表示从这一步到下一步的意思,其实每步还有很多很多的细节。本小节,我们只把步骤3——编译,拿出来展开讲解。

1、明确几个概念

我们的文本代码.cpp需要转化成.exe可执行程序,cpu才可以执行。
从.cpp到.exe的过程,我们就笼统的叫做编译,其实这个过程是需要经过下面3个子过程的:

(1)预处理(Preprocess):从.cpp变成.i文件。是处理一些预处理语句。

(2)编译(compile):从.i文件到.obj文件。这一步是编译的核心。这一步还要分很多个子环节,也是本部分的重点。其实如果你源文件没有引用其他文件或库之类的,就像上图的示例中的源代码,啥也没有,那其实到这步结束,生成.obj文件就已经是二进制可执行文件了,cpu就可以执行了。但事实上,我们不会写上例中那么无聊的代码,至少我们要写一个在屏幕上打印个hello world啥的。那在屏幕上打印东西都已经不是那么简单了,此时你写的源代码就需要引入头文件啥的,那此时生成的.obj文件就没法放cpu上执行,因为.obj文件里面有引用其他文件的代码,而且其他文件代码cpu也不知道,所以就没法执行。所以此时你还得进行链接link。尤其是我们在实际项目开发过程中,我们不仅会有很多引用,我们还会有很多.cpp文件。compile只是对每个.cpp文件分别进行编译,分别生成每个文件的.obj文件。那此时就非常有必要再有个链接器把我们这些所有的.obj文件都链接起来。

(3)链接(Link):从.obj文件到.exe文件。为什么要链接,其实上面已经说得非常清楚了。一般情况下我们的源代码编译都是要进行链接的,链接完毕后就是.exe文件,cpu就可以执行了。

所以,平时我们经常说的编译其实是预处理+编译+链接,其实就是build,build=preprocess+compile+link。如果你的源代码简单到无聊,那你可能就不需要link了,但一定得预处理和编译才能执行。

  • 此外,这里还得梳理几个概念:

(1)编译器是把我们每个.cpp文件都分别编译成独立的.obj文件,所以在这个过程中每个.cpp文件都是一个翻译单元(translation unit),一个翻译单元生成一个obj文件。

在C++中,文件翻译单元是指一个.cpp文件以及它包含的所有头文件组合到一起形成的单独编译单元。每个.cpp文件在编译时都会生成一个翻译单元。例如,假设你有一个名为main.cpp的源文件和一个名为utils.h的头文件。main.cpp包含#include "utils.h"。在编译main.cpp时,utils.h中的所有内容也会被插入到main.cpp中,形成一个翻译单元,然后这个翻译单元会被编译成可执行文件或对象或库代码。
在这个例子中,main.cpp和utils.h组合在一起形成了一个翻译单元,utils.cpp是另一个翻译单元。在编译时,每个.cpp文件都会独立编译成一个对象文件或库或可执行程序。最后,链接器会将这些对象文件和库文件和可执行文件合并成最终的一个可执行文件。

(2)C++是不关心文件名的,不像Java语法,你的类名和你的文件名得一样、你得文件夹层次要和你的package一样。但是C++不一样:在于C++中,文件只是提供给编译器源代码的一种方式。文件名只负责告诉编译器你给它的是什么类型的文件、以及这种类型文件编译器应该如何处理它。比如你给编译器一个.cpp的文件,编译器就把这个文件当作C++文件进行处理;当你给编译器一个.C或者.H文件,编译器会把.C文件当成C语言文件进行处理,而不是当作C++文件进行处理,同时把.H文件当作头文件进行处理。这些都是默认的约定。当然你也可以更改,比如你给编译器一个.abcdef文件,那你就得再告诉编译器这种文件你按照C++文件进行处理,也是可以的。

(3)文件是指一组相关数据的有序集合,这个数据集的名称叫做文件名。例如源程序文件(.cpp)、目标文件(.obj)、可执行文件(.exe)、库文件(头文件)等。文件通常是驻留在外部介质(如磁盘等)上的,在使用时才调入内存中来,这就是为什么对文件操作时需要打开和关闭的原因。
在C++语言中,文件是一个流的概念。头文件fstream定义了三个类型:
ifstream:从一个给定文件读取数据;
ofstream:向一个给定文件写入数据;
fstream:读写给定文件。
类型的操作和cin、cout一样,可以用IO操作符(<<和>>)来读写文件,也可以用getline函数读取一整行数据。

(4)在 C++ 程序中,符号(例如变量或函数名称)可以在其范围内进行任意次数的声明。 但是,一个符号只能被定义一次。 这就是“单一定义规则”(ODR)。

这些概念是我们理解后面的基础,目前我能想到的就是这么多。下面我们就展开编译的3个子过程,详细聊:

2、预处理(Preprocess):
编译器中进行预处理操作(Preprocess)的是预处理器(Preprocessor), 也是第一个上场的程序。预处理器(Preprocessor)是对C++程序源代码进行简单替换和增删的一个操作。
预处理不对程序的源代码进行解析,仅仅是替换或删除某些代码块而已。所以预处理完毕后文件(.i文件)还是一个字符串文件,就是还是都是英文单词和数字那样的字符文件。

那么预处理是如何进行增删替换的呢?当然就是人为给它定义一些规则,符号规则就增删替换。
那也所以,预处理的工作流程就是:逐行遍历.cpp文件中的所有代码行,遇到预处理语句时,根据规则增删。
那么哪些是预处理语句?哪些是要替换的?用谁替换?哪些又是删除的?

上面是预处理原理,下面我们看看预处理的实操过程:

从上图可以看出我们的.cpp文件只有1k,但是build完毕后竟然有几十k,原因就是编译器进行了预处理。

我们对比一下有预处理语句的cpp文件和无预处理语句的cpp文件,编译后的差别:

那么预处理到底是不是简单的替换和删除呢?看下面例子:


为啥Math.cpp编译后没大多少啊?因为仅仅是从EndBrace.h文件中复制了一个后花括号而已。就是我前面说的,预处理仅仅是复制粘贴一些文本而已。这里就是把.h文件中的字符}复制到Math.cpp中的include那行位置上而已,或者说就是替换了include那行而已。那到底是不是你说的啊?我们看看生成的预处理文件.i文件就能证明了:


可见是不是就是仅仅是用头文件中的}替换了源文件中include那行代码,就是一个删除并替换的动作。下面我们再看看其他情况下的.i文件是不是也是按照前面说的规则生成的:

可见,在预处理过程中主要就是,处理源代码中的预处理语句:引入头文件(替换)、去除注释(删除)、处理所有的条件编译指令(该删除的删除,不该的留下),宏的替换(替换),添加行号(添加),保留所有的编译器指令(添加)。

以上就是预处理,下面我们看更核心部分:

3、编译(Compile)

待续。。。。

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

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

相关文章

Java基础面试重点-1

0. 符号&#xff1a; *&#xff1a;记忆模糊&#xff0c;验证后特别标注的知识点。 &&#xff1a;容易忘记知识点。 *&#xff1a;重要的知识点。 1. 简述一下Java面向对象的基本特征&#xff08;四个&#xff09;&#xff0c;以及你自己的应用&#xff1f; 抽象&#…

c#调用c++dll方法

添加dll文件到debug目录&#xff0c;c#生成的exe的相同目录 就可以直接使用了&#xff0c;放在构造函数里面测试

【git使用四】git分支理解与操作(详解)

目录 &#xff08;1&#xff09;理解git分支 主分支&#xff08;主线&#xff09; 功能分支 主线和分支关系 将分支合并到主分支 快速合并 非快速合并 git代码管理流程 &#xff08;2&#xff09;理解git提交对象 提交对象与commitID Git如何保存数据 示例讲解 &a…

CorelDRAW2024官方最新中文破解版Crack安装包网盘下载安装方法

在设计的世界里&#xff0c;软件工具的更新与升级总是令人瞩目的焦点。近期&#xff0c;CorelDRAW 2024中文版及其终身永久版的发布&#xff0c;以及中文破解版Crack的出现&#xff0c;再次掀起了设计圈的热潮。对于追求专业精确的设计师而言&#xff0c;了解这些版本的下载安装…

Jemeter做性能测试

目录 1. 测试计划 2. 线程组 3. HTTP请求 4. 查看结果树 5. 聚合报告 【要求】 用JMeter取样器&#xff0c;实现对云边AI (qinzhi.xyz)的访问 【步骤】 1. 测试计划 2. 线程组 右击测试计划——添加——线程(用户)——线程组 3. HTTP请求 右击线程组——添加——取样…

鸿蒙轻内核A核源码分析系列五 虚实映射(6)虚拟映射修改转移

6.1 映射属性修改函数LOS_ArchMmuChangeProt 函数LOS_ArchMmuChangeProt用于修改进程空间虚拟地址区间的映射保护属性&#xff0c;其中参数archMmu为进程空间的MMU结构体&#xff0c;vaddr为虚拟地址&#xff0c;count为映射的页数&#xff0c;flags为映射使用的新标签属性信息…

easyrecovery专业版破解无需注册绿色版免费下载 easyrecovery16数据恢复软件永久激活码密钥百度网盘crack文件

EasyRecovery &#xff08;易恢复中国&#xff09;是由全球著名数据厂商Ontrack 出品的一款数据文件恢复软件。支持恢复不同存储介质数据&#xff1a;硬盘、光盘、U盘/移动硬盘、数码相机、Raid文件恢复等&#xff0c;能恢复包括文档、表格、图片、音视频等各种文件。 开发背景…

【网络安全的神秘世界】2024.6.6 Docker镜像停服?解决最近Docker镜像无法拉取问题

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 解决Docker镜像无法拉取问题 &#x1f64b;‍♂️问题描述 常用镜像站&#xff1a;阿里云、科大、南大、上交等&#xff0c;全部挂掉 执行docker pull命…

手机数据删除很意外?失而复得,2个技巧揭晓

在这个快节奏的时代&#xff0c;手机就像是我们的“第二心脏”&#xff0c;不仅用来跟人聊天&#xff0c;还藏着我们的秘密宝藏——个人数据。但有时候&#xff0c;手一抖&#xff0c;心一慌&#xff0c;重要数据就消失了&#xff01;是不是感觉天都要塌下来了&#xff1f;别怕…

翻译: Gen AI生成式人工智能学习资源路线图一

Introduction 介绍 本文档旨在作为学习现代人工智能系统背后的关键概念的手册。考虑到人工智能最近的发展速度&#xff0c;确实没有一个好的教科书式的资源来快速了解 LLMs 或其他生成模型的最新和最伟大的创新&#xff0c;但互联网上有大量关于这些主题的优秀解释资源&#x…

postman教程-21-Newman运行集合生成测试报告

上一小节我们Postman Newman的安装方法&#xff0c;本小节我们讲解一下Postman Newman的具体使用方法。 使用Newman运行集合 1、导出Postman集合&#xff1a; 在Postman中&#xff0c;选择你想要运行的集合&#xff0c;然后点击“导出”按钮&#xff0c;选择导出为“Collect…

Figma文字标注工具的使用方法是什么?

在UI设计过程中&#xff0c;有一个让设计师头疼的工作环节&#xff0c;那就是文字标注的问题。相信大家对Figma软件都很熟悉&#xff0c;但是这个软件的使用也有自己的缺点&#xff0c;就是文字标注等问题&#xff0c;日常使用自己是做不到的&#xff0c;需要依靠第三方工具来执…

推荐使用三丰云免费云服务器、免费虚拟主机

官网地址&#xff1a;www.sanfengyun.com 三丰云服务器&#xff1a; 配置高&#xff1a;能够轻松运行应用程序和网站&#xff0c;在处理大量请求和保持高可靠性方面表现出色。 易用性好&#xff1a;界面直观、简单&#xff0c;能够轻松管理服务器和资源&#xff0c;快速创建和…

面向计算病理学的通用基础模型| 文献速递-视觉通用模型与疾病诊断

Title 题目 Towards a general-purpose foundation model for computational pathology 面向计算病理学的通用基础模型 01 文献速递介绍 组织图像的定量评估对于计算病理学&#xff08;CPath&#xff09;任务至关重要&#xff0c;需要从全幻灯片图像&#xff08;WSIs&…

访问方法(反射)

文章目录 前言一、访问成员方法的方法二、Method类 1.常用方法2.实操展示总结 前言 为了实现在某类中随时可以调用其他类的方法&#xff0c;java.lang.reflect包中提供了Method方法类来实现该效果。每一个Method对象代表着一个方法&#xff0c;利用Methoc对象可以操纵相应的方法…

高效处理风电时序数据,明阳集团的 TDengine 3.0 应用实录

作为全国 500 强企业&#xff0c;明阳集团在风电行业拥有领先实力。目前全球超过 800 个项目采用明阳各种型号风电机组&#xff0c;安装数量超过 15000 台。每台风电机组配备数百至上千个监测点&#xff0c;生成的时序数据每秒一条&#xff0c;每天产生亿级以上的数据量。这些数…

Android RTSP/RTMP多路播放时动态切换输出View类型(SurfaceView和TextureView 动态切换)

SurfaceView和TextureView的区别和优缺点等, 相关的资料很多. 从Android低延时播放器实现角度来看, 总结了下主要区别有: 1. MediaCodec输出到SurfaceView延时一般比到TextureView更低. 2. MediaCodec用SurfaceView比TextureView占用的资源一般更少些(CPU和内存都小一些, 不过还…

【STM32】输入捕获应用-测量脉宽或者频率(方法1)

图1 脉宽/频率测量示意图 1 测量频率 当捕获通道TIx 上出现上升沿时&#xff0c;发生第一次捕获&#xff0c;计数器CNT 的值会被锁存到捕获寄存器CCR中&#xff0c;而且还会进入捕获中断&#xff0c;在中断服务程序中记录一次捕获&#xff08;可以用一个标志变量来记录&#…

Spring Cloud Netflix 之 Ribbon

前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff1a;https://www.captainbed.cn/z ChatGPT体验地址 文章目录 前言前言1、负载均衡1.1、服务端负载均衡1.2、客户端负载均衡 2、Ribbon实现服务…

uni-im:云端一体、全平台、免费开源的即时通讯系统解析

一、引言 随着移动互联网的迅猛发展&#xff0c;即时通讯&#xff08;IM&#xff09;系统已成为人们日常沟通不可或缺的一部分。然而&#xff0c;开发一个稳定、高效、跨平台的IM系统并非易事。为了降低开发成本、提高开发效率&#xff0c;越来越多的开发者开始寻找成熟的开源…