掘金 后端 ( ) • 2024-04-16 09:28

image.png

软件安全一直是软件开发和互联网行业中的一个必先要考虑的问题,而编程语言的内存安全则是软件安全的一个重要分支。在编程语言中,内存安全的重要性体现在多个方面,对于程序的稳定性、可靠性以及安全性都具有至关重要的影响。

什么是内存安全的编程语言?

内存安全的编程语言是指那些能够自动管理内存,向编程人员隐藏内存布局,防止内存损坏错误的编程语言。

内存安全有助于防止程序崩溃和错误。当编程语言能够自动管理内存,避免内存泄漏、野指针等内存安全问题时,程序更有可能稳定运行。其次,内存安全能够提高软件的质量和可靠性。采用内存安全的编程语言可以减少软件中的缺陷和漏洞,因为这类语言通常使用垃圾回收机制或智能指针等机制来自动管理内存,降低了手动管理内存带来的风险。因此,内存安全在编程语言中具有至关重要的作用。

例如,Java就是一种典型的内存安全编程语言。它使用垃圾回收机制来自动管理内存,程序员不需要手动分配和释放内存,这大大降低了内存泄漏和野指针等问题的风险。类似地,Go、C#和Python也是使用垃圾回收机制或引用计数来自动管理内存的内存安全编程语言。

与此相对,C和C++等语言则被认为是内存不安全的,因为它们允许直接操作内存地址,并且缺乏边界检查,因此在内存安全性方面常常陷入困境。

内存不安全时可能会发生什么?

如果内存不安全,可能会发生一系列严重的问题。以下是一些可能发生的情形:

  1. 内存泄漏:当程序在申请内存后,未能正确地释放已分配的内存时,就会发生内存泄漏。随着时间的推移,这可能导致可用内存逐渐耗尽,影响系统的性能,甚至导致系统崩溃。
  2. 野指针:当一个指针指向的内存已经被释放或者从未被分配时,它就被称为野指针。尝试访问或操作野指针会导致不可预知的行为,可能包括程序崩溃、数据损坏,或者更严重的安全问题。
  3. 缓冲区溢出:如果程序在写入数据时超出了缓冲区的界限,就会覆盖相邻的内存区域。这可能导致数据损坏、程序崩溃,或者更糟糕的是,攻击者可以利用这种溢出来执行恶意代码,即所谓的“缓冲区溢出攻击”。
  4. 数据损坏:不安全的内存操作可能导致数据损坏,例如,写入错误的数据类型到内存区域,或者读取未初始化的内存。
  5. 程序崩溃:当内存错误达到一定程度时,程序可能会突然崩溃,造成数据丢失或者服务中断。
  6. 安全漏洞:内存不安全可能导致安全漏洞,攻击者可以利用这些漏洞执行任意代码、提升权限或窃取敏感信息。

例如,在C或C++这类内存不安全的编程语言中,如果程序员没有正确地管理内存(如忘记释放动态分配的内存、越界访问数组等),就可能出现上述问题。因此,使用内存安全的编程语言或采取额外的安全措施(如使用智能指针、进行内存边界检查等)对于保障软件的安全性和稳定性至关重要。

回顾计算机操作系统

冯·诺依曼体系结构将计算机划分为五个基本部分:输入设备、输出设备、存储器、运算器和控制器。输入设备负责接收和处理外部信息,输出设备用于展示计算结果或信息,存储器用于存储程序和数据,运算器负责执行算术和逻辑运算,而控制器则负责协调和指挥计算机的各个部件按照指令序列进行工作。

image.png

计算机存储器

我们细化一下存储器部分,依照功能计算机存储器可分为三种:

  • 主存储器(内存):临时存储设备,用于存储CPU正在处理或即将处理的数据和程序。内存按工作方式的不同又可以分为俩部分:

    RAM:随机存储器,可以被CPU随机读取,一般存放CPU将要执行的程序、数据,断电丢失数据

    ROM:只读存储器,只能被CPU读,不能轻易被CPU写,用来存放永久性的程序和数据,比如:系统引导程序、监控程序等。具有掉电非易失性。

  • 辅助存储器(外存):如硬盘、光盘、U盘等,用于长期保存数据,但交换速度较慢。

  • 高速缓冲存储器(Cache):一种位于主存和CPU之间的快速小容量存储器,用于暂时存放CPU最近访问过的数据或指令。一般采用静态RAM充当Cache。

image.png

而这三种其中我们编写的应用程序打交道最多的就是内存,我们继续细化下内存。

内存的管理方式

现代操作系统中,计算机内存的管理通常是按照先分段再分页的方式进行的。

分段机制主要是为了解决程序直接使用物理地址时可能遇到的问题,例如两个程序使用的地址有交集时无法同时运行,写代码时需要考虑到目标计算机的内存大小,以及系统程序和各个程序之间需要隔离等。通过将内存分成若干段,每个程序只能访问为其分配的段,从而实现了内存的有效管理和保护。

分页机制则是对内存进行更细粒度的管理。它将内存划分为固定大小的页面,每个页面都可以单独进行分配和管理。分页机制可以更有效地利用内存空间,并提供了更好的内存保护机制。

因此,计算机内存管理既涉及到分段也涉及到分页,两者是相辅相成的。分段为程序提供了初步的隔离和保护,而分页则进一步提高了内存管理的效率和灵活性。这种结合使用的方式使得现代操作系统能够更有效地管理计算机内存,支持多任务运行和复杂的应用程序。

内存与应用程序的关系

在应用程序中我们使用指针时经常会接触到一个关键词:内存地址

那么什么是内存地址?

CPU执行程序、处理数据都要和内存打交道,这个打交道的方式就是内存地址。

编程语言中使用的指针地址是内存中的段地址还是页地址?

在编程语言中,特别是在C语言中,使用的指针地址是指向内存中某个特定位置的直接地址。这种地址通常用于直接访问和操作内存中的数据,从而实现更加灵活高效的编程。指针地址并不是特指内存中的段地址或页地址,而是指向内存中的实际存储位置。

段地址和页地址是计算机内存管理中的概念。在早期的计算机体系结构中,物理内存被划分为多个段,每个段的起始地址就是段地址。而页地址则与分页内存管理相关,现代操作系统常常使用分页机制来管理物理内存,将内存划分为固定大小的页,每页有一个唯一的页地址。

然而,在编程语言如C中,程序员通常不需要直接处理段地址或页地址。指针提供的是一种抽象,它允许程序员以更高级的方式与内存交互,而无需关心底层的内存管理细节。当程序员使用指针时,他们操作的是逻辑上的内存地址,这些地址与具体的物理内存布局(如段或页)是解耦的。

因此,编程语言中使用的指针地址既不是段地址也不是页地址,而是直接指向内存中的某个存储位置。 这种地址对于程序员来说是透明的,它们允许程序员在不知道或不需要知道底层内存管理细节的情况下,直接访问和操作内存中的数据。

最后,内存不安全的编程语言一定不好吗?

内存不安全的编程语言并不一定不好,这主要取决于具体的使用场景和需求。虽然内存不安全的编程语言(如C和C++)可能存在内存泄漏、越界访问等安全风险,但它们在某些特定领域和场景中仍然具有独特的优势。

首先,内存不安全的编程语言通常提供了更高的性能和更底层的控制能力。 这些语言允许程序员直接管理内存,进行细粒度的优化,从而在某些性能要求极高的场景下表现出色。例如,在操作系统、游戏引擎、嵌入式系统等领域,C和C++等语言仍然是首选。

其次,内存不安全的编程语言在某些特定场景下可能更加灵活和高效。在某些需要直接与硬件交互、实现底层协议或进行特定性能优化的任务中,程序员可能需要更直接地控制内存布局和访问方式。这些情况下,使用内存不安全的编程语言可能更为合适。

综上所述,内存不安全的编程语言并非一定不好,它们在某些特定场景和需求下仍然具有优势。然而,在使用这些语言时,需要谨慎处理内存管理问题,以确保程序的稳定性和安全性。同时,随着技术的不断发展,我们也期待未来能够出现更多既安全又高效的编程语言选择。