小白学习C++ 教程十六C++ 中的动态内存分配

Posted 刘润森!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小白学习C++ 教程十六C++ 中的动态内存分配相关的知识,希望对你有一定的参考价值。

@Author:Runsen

前言

C/C++ 中的动态内存分配是指由程序员手动进行内存分配。对于“int a”、“char str[10]”等普通变量,内存会自动分配和释放。对于像“int *p = new int[10]”这样的动态分配内存,程序员有责任在不再需要时释放内存。如果程序员不释放内存,则会导致内存泄漏(直到程序终止内存才会释放)。

内存分为两部分:

  1. 堆栈

在堆栈中,函数内部声明的所有变量都从堆栈中占用内存。

堆是程序未使用的内存,用于在程序运行时动态分配内存。

因此,动态分配的内存在堆上分配,非静态和局部变量在堆栈上分配内存。

C 程序的典型内存

C 程序的典型内存表示由以下部分组成。

  1. 文本段
  2. 初始化数据段
  3. 未初始化数据段
  4. 堆栈

文本段

文本段,也称为代码段或简称为文本,是目标文件或内存中程序的一部分,其中包含可执行指令。
作为内存区域,可以将文本段放置在堆或堆栈下方,以防止堆和堆栈溢出覆盖它。

通常,文本段是可共享的,因此对于频繁执行的程序(例如文本编辑器、C 编译器、shell 等),只需要在内存中保留一个副本。此外,文本段通常是只读的,以防止程序意外修改其指令。

初始化数据段

初始化数据段,通常简称为Data Segment。数据段是程序虚拟地址空间的一部分,它包含由程序员初始化的全局变量和静态变量。

请注意,数据段不是只读的,因为变量的值可以在运行时更改。

初始化数据段可以进一步分为初始化只读区初始化读写区

例如,C 中由char s[] = “hello world”定义的全局字符串和main函数外的int debug=1之类的C 语句将存储在已初始化的读写区域中。

但是像const char* string = “hello world”这样的C语句,使字符串“hello world”存放在初始化只读区,字符指针变量string存放在初始化读写区,因此该变量不可修改。

还有static int i = 10 将存储在初始化只读区中。

未初始化的数据段:

未初始化的数据段的数据在程序开始执行之前由内核初始化为 0,未初始化的数据段通常称为“ bss ”段

例如,声明为static int i的变量;将包含在 BSS 段中。
例如,一个声明为 int j的全局变量;将包含在 BSS 段中。

堆栈

堆栈区域,在传统上与堆区域相邻并朝相反的方向增长;当堆栈指针遇到堆指针时,空闲内存被耗尽。

堆栈区域包含程序堆栈,一个 LIFO 结构,通常位于内存的较高部分。

堆栈,存储自动变量的位置,以及每次调用函数时保存的信息。

每次调用函数时,返回的地址和有关调用者环境的某些信息(例如某些机器寄存器)都保存在堆栈中。

新调用的函数然后在堆栈上为其自动和临时变量分配空间。这就是 C 中递归函数的工作方式。

每次递归函数调用自身时,都会使用一个新的堆栈帧,因此一组变量不会干扰来自该函数另一个实例的变量。

非静态和局部变量在堆栈上分配内存

堆是通常发生动态内存分配的段。

Heap 区由 malloc、realloc 和 free 管理,可以使用 brk 和 sbrk 系统调用来调整其大小。Heap 区域由进程中的所有共享库和动态加载的模块共享。

[ng@CentOS]$  vim memory-layout.c 
#include <stdio.h>
 
int main(void)
{
    return 0;
}
[ng@CentOS]$ gcc memory-layout.c -o memory-layout
[ng@CentOS]$  size memory-layout
text       data        bss        dec        hex    filename
960        248          8       1216        4c0    memory-layout

size 命令报告文本、数据和 bss 段的大小(以字节为单位)

在程序中添加一个全局变量,现在检查 bss 的大小

[ng@CentOS]$  vim memory-layout.c 

#include <stdio.h>
 
int global; /* 未初始化的数据 in bss*/
 
int main(void)
{
    return 0;
}

[ng@CentOS]$ gcc memory-layout.c -o memory-layout 
[ng@CentOS]$ size memory-layout 
text data bss dec hex filename 
 960 248  12  1220 4c4 memory-layout

初始化静态变量,然后将其存储在数据段(data)

[ng@CentOS]$  vim memory-layout.c 

#include <stdio.h>
 
int global; /* 未初始化的数据 in bss*/
 
int main(void)
{
    static int i = 100; /* 初始化变量储存在DS*/
    return 0;
}

[ng@CentOS]$ gcc memory-layout.c -o memory-layout 
[ng@CentOS]$ size memory-layout 
text data bss dec hex filename 
960  252 12 1224 4c8 memory-layout

在 C++ 中如何分配/释放内存

C 使用malloc()calloc()函数在运行时动态分配内存,并使用free()函数释放动态分配的内存。C++ 支持这些函数,并且还有两个运算符new和delete,它们以更好、更简单的方式执行分配和释放内存的任务。

new 运算符

new 运算符表示分配内存的请求。如果有足够的内存可用,new 操作符会初始化内存并将新分配和初始化的内存的地址返回给指针变量。

使用 new 运算符的语法:要分配任何数据类型的内存,语法为:

指针变量 = new 数据类型;

这里,指针变量是数据类型类型的指针。数据类型可以是任何内置数据类型,包括数组或任何用户定义的数据类型,包括结构和类。初始化内存:我们也可以使用 new 操作符来初始化内存:

// 用 NULL 初始化的指针,然后为变量请求内存
int *ptr; //声明一个指针ptr
ptr = new int; //动态分配一个int并将地址加载到ptr中   

// 结合指针声明及其赋值
int *p = new int;

// Example
int *p = new int(25);
float *q = new float(75.25);

分配内存块: new 运算符也用于分配数据类型的内存块(数组)

Example:

int *p = new int[10]

声明普通数组和使用 new 分配内存块的数组之间存在差异。

最重要的区别是,普通数组由编译器释放(如果数组是本地的,则在函数返回或完成时释放)。然而,动态分配的数组总是保留,直到它们被程序员释放或程序终止。

如果在运行时没有足够的内存可用,则抛出类型为 std::bad_alloc的异常指示失败。对此,解决的办法nothrow与 new运算符一起使用,在这种情况下它返回一个 NULL 指针

int *p = new(nothrow) int; 
if (!p) 
{ 
   cout << "内存分配失败\\n"; 
}

delete 操作符

如果不再需要动态分配给变量的内存,则可以使用 delete 运算符释放内存。

Example:

delete  ptr;//释放ptr指向的内存

示例:演示 new 和 delete 运算符的工作原理

#include <iostream>
using namespace std;
int main ()
{
     int *ptr  = NULL;        // 指针初始化为 null
     ptr  = new int;          // 请求内存
     *ptr = 12345;        // 将值存储在分配的地址
     cout << "指针变量的值 *ptr :" << *ptr << endl;
     delete ptr;           // 释放内存。
     return 0;
}

输出:

指针变量的值 *ptr : 12345

以上是关于小白学习C++ 教程十六C++ 中的动态内存分配的主要内容,如果未能解决你的问题,请参考以下文章

小白学习C++ 教程七在C++指针声明和指针相关概念

小白学习C++ 教程十一C++类中访问修饰符

什么是 C++ 中的动态内存分配?

什么是 C++ 中的动态内存分配?

小白学习C++ 教程十七C++ 中的字符数组和字符串常见的函数

经典问题解析四(四十六)