C++程序实战操作

对于一个C++程序员来说,可能更多是是每天都在跟各种上层语义、设计模式、软件方法等等在打交道。但对于「一个C++程序是如何运行在机器上的」这件事可能会比较陌生。有时,遇到一些问题,在宏观角度看起来可能比较难以解释,但其实从底层出发,就能发现这个问题其实根本不算问题。类似的问题有:

  1. 空指针到底能不能访问?(int *p = nullptr; *p = 5;)

  2. 给一个变量取地址,取到的是不是物理地址?(int a; std::cout << &a;)

  3. 操作一个常数地址是否合法?(*(int *)0xa0000 = 0x41;

  4. 全局变量、静态局部变量、字符串字面量等在内存中是如何布局的?

  5. C/C++程序如何编译为内核代码,运行在内核态程序上?

  6. gdb过程中,看到的寄存器是否是真实的?

上面这些疑问,有一些是被读者问到的,还有一些是笔者曾经思考过,但没有很快解决的。与此同时,笔者发现,中层、通用性的教程比比皆是,但高层和底层的、专精型的教程却是少之又少。很多问题可能其实很简单,但就是搜不到相关的教程。笔者也曾尝试到一些系统讲解底层的书籍中寻找答案,但也发现,它们在各自突出的领域中讲解地很详细,但对于上下层串联的部分却总是有缺失,导致各个领域的知识是破碎的,难以关联在一起,以建立一个更加宏观的体系。

于是在经过了一系列研究和实验之后,笔者决定起笔这一篇文章。在这篇文章中将会介绍:

  1. x86体系的结构和启动过程

  2. 如何编写一个简单的MBR(Master Boot Record),然后进入内核程序

  3. 如何从用C/C++来生成内核程序(包括编译、链接、转载的方法)

  4. 站在内核的角度看到的内存结构是怎样的

  5. C/C++程序的内存分布是怎样的,各部分加载到内存中的形态是怎样的

  6. C代码和C++代码编译方式的异同

关于本文,有以下几点说明:

  1. 本文的底层逻辑以x86体系为例,C/C++代码也会生成x86体系的机器码。

  2. 虽然我们的项目是x86架构的,但即便你使用的是ARM架构的设备(例如搭载苹果自研M系列芯片的Mac)也没有关系,笔者会介绍可以在ARM版macOS上编译和运行x86程序的方法,会使用跨平台运行的模拟器。

  3. 本文使用到的工具都是业界通用的、能轻易在网上下载到并且很容易找到对应说明文档的软件,不会使用笔者自研的黑盒程序,并且会详细介绍每一种工具的部署、使用方法,保证读者可以完成实验

  4. 对于一些历史发展历程,和一些历史遗留问题的诞生,笔者会花一部分篇幅来「讲故事」,如果读者不感兴趣,则可以跳过相关的篇幅,直接看结论即可。

如果你准备好了的话,我们马上开始!

x86体系架构

相信读者对x86这个词肯定不陌生,那么它到底指的是什么呢?

指令集

对于一个CPU来说,其实就是一个高集成的逻辑电路。如果你玩过数字电路的话,一定会知道所谓的「与」「或」「非」门电路,用这些门电路组合起来,我们就可以实现更多更复杂的功能。

不过逻辑电路再复杂,无非也就是把「一组输入的电信号」转换为「一组输出的电信号」,这就是它最基本的功能。比如说,某一个芯片有3个输入引脚,2个输出引脚,当我给输入引脚分别给「高电平,高电平,低电平」的时候,它能在输出引脚给我「低电平,高电平」这样的信号。在刚才这段描述中,「芯片的输入、输出引脚个数」称为「芯片的接口规模」,而「当给XXX输入信号的时候,能给我YYY输出信号」则称为「芯片的逻辑功能」。

因此,我们把那些「可以用来输出的信号」就称作「指令」,而这个芯片能够支持的所有「指令」的集合,就称为「指令集」。因此,一个CPU的指令集直接决定了它的原始功能。

而x86体系架构使用的这种指令集,我们就可以叫他x86指令集,用来描述所有x86体系架构的CPU能够支持哪些指令。

当然,除了最核心的指令集以外,「体系架构」自然还包括CPU的其他部件要有哪些,以及跟外部硬件应当如何交互。总之,我们可以认为这是一套协议标准,当我们使用了x86体系的CPU以后,它一定会含有哪些部件、怎么给它指令它就能正常运行、外部的硬件应当如何布局等等这些问题就已经确定了。我们只需要按照它所规定的协议来编写程序,就可以在这个体系上正常运行了。

为什么叫x86?

解释完x86是什么了以后,相信一定会有读者好奇,这种架构为什么叫这个名字?它和我们现在市面上主流的硬件设备是什么样的关系?

故事要从1978年开始说起。1978年,Intel公司推出了一款CPU,型号叫8086(至于为啥叫这个数字,估计只能问Intel了……)。其实在当年,这款CPU也没激起多大的浪花,我们现在大家都去研究它,也不过是幸存者偏差罢了。所以我们只需要知道,20世纪70年代末,一个姓英的公司(英特尔)发布了一款芯片,型号为8086。

8086芯片没有太大的动静,这有一个非常关键的问题,就是它太贵了!因为它要卖360美元一个。注意!这仅仅是CPU的价钱,没有算其他的硬件。所以能用得起的一般都是极个别的企业,个人用户可谓望尘莫及了。而真正让这个系列的芯片火起来的是8088。

8088我们可以认为是8086的一个精简版,或者我们可以戏称为「8086 SE」~。1981年,IBM使用了8088芯片,生产了面相个人的PC,价格亲民,因此在全球范围内火了起来,也就带动了这个系列的芯片的销量。

此后,Intel就开始了这个架构的CPU的研发迭代,后续又推出了80186、80286、80386。它们都兼容8086的工作模式,但在这个过程中还是出现了一些小插曲(或者可以理解为小bug,这个后续章节会涉及)。

由于这个系列都以86结尾,因此就管这个系列叫做「x86」系列。但注意,「x86架构」则是专指80386以及以后的芯片,而不包括8086、80186和80286,原因我们会在后续章节解释。

直到1992年,本应叫「80586」的CPU诞生之前,Intel因为一些商标版权的问题,使得这个系列不得不改名,当时的80586上市时,名为「Pentium」,中文译作「奔腾」。

后续Intel又发布了「Celeron(赛扬)」系列,还有「Core(酷睿)」系列,以及「Xeon(至强)」系列,都沿用了x86架构,Intel将其称为「IA-32架构」,它们都保持着向下兼容。

x64和x86的关系

故事的转折点在2001年,那个时候有人觉得x86架构有缺陷,不应该继续沿用,于是推出了一款全新的架构,称之为「IA-64」架构,并推出了这个架构的处理器——「Itanium(安腾)」系列。

这里的IA指的是Intel Architecture,而64表示它的指令字长(后续会重点解释)。本来这个命名的目的也很明确,曾经的是「IA-32」,现在重新设计以后叫做「IA-64」。但是因为它并没有向下兼容IA-32,并且价格昂贵,因此在个人PC领域并没有溅起水花。而它主打的服务器领域则是没有拼过IBM的PowerPC,所以也没有太多市场。这也导致了安腾系列的CPU至今都不是很出名。

IA-64不成功,但另一个64位架构却火了,这就是AMD公司在1999年首次推出的AMD64架构。后续AMD64架构被广泛用于个人PC上。那么,AMD64的魅力在哪?其实就在于,它兼容了IA-32架构,并在此之上进行了扩展。因此,AMD64架构也被称为「x86-64」架构,也就是扩展64位的x86架构。

所以这里就有一个很有意思的现象,IA-64作为IA-32的继承者,并没有兼容IA-32,并且没落了。反而是AMD64夺得了王冠,向下兼容IA-32。由于AMD64架构的成功,后续也被Intel所使用,并将其命名为Intel 64。

其实Intel 64和AMD64基本没有区别,主要还是商业竞争中刻意区分了它们。但是硬件厂商的这些商业竞争,对于这些软件公司来说无足轻重,他们只关心,我的软件适配哪种架构,就够了。因此,他们无论描述为「AMD64」还是「Intel 64」,都似乎有站队的嫌疑,而又因为Intel 64和AMD64其实就是同一套架构,因此这些软件厂商又把这种架构称为「x64架构」,其中「x」你自己脑补把,Intel也行,AMD也行。

因此我们总结一下:

  1. x86架构又叫IA-32架构,是从Intel 80386芯片开始所使用的架构,向下兼容8086和80286架构

  2. IA-64架构不兼容IA-32,仅用于Intel Itanium系列

  3. x86-64架构向下兼容IA-32架构,又被称为AMD64架构和Intel 64架构,进而合称x64架构,目前市面上绝大多数的个人PC使用的就是这种架构(包括Intel的酷睿、至强,AMD的锐龙系列)

值得注意的是,由于x64是向下兼容x86的,因此在很多人口中,并不会区分它们,又因为x86架构已经过时很久了,现在很少有设备会去使用。因此有时我们听到「x86」其实指的就是x64架构,尤其是跟ARM架构放在一起描述的时候(比如我们经常会说,苹果从x86转向了ARM,但其实这里的x86指的是x64,而非真正的IA-32架构)。

所以为了避免混淆,笔者在本系列文章中,统一用「IA-32架构」和「AMD64」架构的名称,而不使用「x86」这种可能有二义性的词汇。

为什么选择AMD64架构?

因为这是当前市面上使用最多的架构。随处可见的Intel Core处理器,AMD Ryzen处理器使用的都是AMD64架构。并且,最常用作服务器的Intel Xeon处理器也是这个架构的,所以我们了解最主流的架构自然是不亏的。

另一方面,也正是因为这是目前的主流架构,因此它的相关资料也是最全、最好找的,黑盒较少,比较透明,所以学习门槛较低。计算机底层专业课程的各主流教材也都是选用了这个架构为例进行讲解的。

既然我们是为了理清程序的构建和运行相关知识,那么架构这里就不要让它成为我们的极大困难点,于是,笔者「毅然决然地」选择了它。(偷笑,其实是因为别无选择~)

搭建虚拟环境

了解完这个架构的情况以后,我们接下来要做的就是找机器,然后进行开发了。

硬件环境

既然是要给AMD64架构的设备进行开发,那么首先,我们得先有一个AMD64架构的硬件设备才行。首先最容易想到的,就是真实地搞一台AMD64架构的电脑。

这方法最直接,但是成本有点高,而且装载程序可能没那么方便。当然了,如果你手边正好有空闲的设备,或者已经不用的老设备,那自然无可厚非。你可以把程序直接运行在真机上,也会有一个不一样的体会,而且满满的仪式感,很酷!

虚拟环境

如果没有,那也没关系,因为我们可以用虚拟机。关于虚拟机的运行,通常有两种方式:

  1. 通过虚拟化(Virtualization)技术运行

  2. 通过软件模拟(Simulation)运行

这里~翻译必须出来背个锅了!Virtualization和Simulation是完全不同的两种虚拟技术,但这里的翻译似乎完全没有把它们区分开,「虚拟化」和「模拟」到底什么区别?反正,从字面上……我是区别不开…………

那么这两者究竟指什么呢?首先我们要知道,要想通过软件的方式模拟一台硬件设备,那这个「软件」应当是运行在已经良好运行的操作系统上了。换句话说,我们要用操作系统开启一个应用程序,然后在这个应用程序中,模拟出硬件设备的各种部件,再利用这种模拟出的部件来执行指令。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
《Visual C++开发实战1200例》是一本系统介绍和实践指导Visual C++开发的书籍。书中涵盖了丰富的实例,旨在帮助读者快速掌握Visual C++的核心知识和开发技巧。 本书首先介绍了Visual C++的基础知识,包括环境搭建、项目创建、工程设置等。然后,通过一系列实例演示了如何使用Visual C++进行窗体设计、控件布局、事件处理等常见的图形界面开发操作。这些实例从简单到复杂,循序渐进,易于理解和模仿。 此外,该书还深入讲解了Visual C++中与数据库、网络通信、多线程编程等相关技术的应用。通过这些实例,读者可以学会如何使用Visual C++连接数据库、进行数据的增删改查操作,以及如何编写网络服务器和客户端应用程序,实现网络通信等。 《Visual C++开发实战1200例》还涉及一些高级主题,如MFC框架的使用、图形绘制和动画效果、多媒体处理等。这些实例不仅让读者对Visual C++的整体框架有更深入的了解,还可以帮助读者进一步提升自己的开发水平。 总之,《Visual C++开发实战1200例》是一本适合初学者和有一定基础的读者的实用教材。通过学习和实践,读者可以快速上手Visual C++开发,掌握核心技能,并在实际项目中灵活应用。无论是希望从零开始学习Visual C++,还是希望进一步提升自己的编程能力,都可以从这本书中获益匪浅。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值