当前位置: 首页>编程语言>正文

【C++】 CC++内存管理

1、C语言内存分配问题

int globalVar = 1;
static int staticGlobalVar = 1;

void Test()
{
   static int staticVar = 1;
   int localVar = 1;
   
   int num1[10] = { 1, 2, 3, 4 };
   char char2[] = "abcd";
   const char* pChar3 = "abcd";
   int* ptr1 = (int*)malloc(sizeof(int) * 4);
   int* ptr2 = (int*)calloc(4, sizeof(int));
   int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
   free(ptr1);
   free(ptr3);
}

1.选择题:   选项: A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)  

globalVar在哪里?____   staticGlobalVar在哪里?____   staticVar在哪里?____   localVar在哪里?____   num1 在哪里?____    

char2在哪里?____   *char2在哪里?___   pChar3在哪里?____      *pChar3在哪里?____   ptr1在哪里?____        *ptr1在哪里?____

2.填空题:  

sizeof(num1) = ____;sizeof(char2) = ____;      strlen(char2) = ____;   sizeof(pChar3) = ____;     strlen(pChar3) = ____;   sizeof(ptr1) = ____;

3.sizeof 和 strlen 区别?

解析:

1

int globalVar = 1;

在main外部,将globalVar定义在了全局,放在了C静态区。

static int staticGlobalVar = 1;

在main外部,也是将staticglobalVar定义在了全局,也存储在C静态区。

两者区别:

没有被static修饰的全局变量在整个程序中都是可见的,可以被其他文件访问和修改;而被static修饰的全局变量只能在定义该变量的文件内部可见,无法被其他文件访问。

static int staticVar = 1;

用static修饰的局部变量会在整个程序执行过程中保留其值,直到程序结束。而staticVar也被存到的C静态区。

int localVar = 1;

一个普通的局部变量定义,存储在A栈区。

int num1[10] = { 1, 2, 3, 4 };

是一个数组定义过程,也是一个普通变量,被存储在A栈区。

char char2[] = "abcd";

是一个数组定义过程,不过是在数组内部承载了一个字符串,此时会将字符串一个一个字符拷贝进数组中,所以最后char2是一个指针,而*char2是一个数组或者是首元素。都被存在A栈区。

const char* pChar3 = "abcd";

用一个const指针接收了字符串 "abcd"的地址,对于pChar3本身,是一个指针,存储在A栈区。对于*pChar3,则是这个常量字符串 "abcd",存储在D常量区。

int* ptr1 = (int*)malloc(sizeof(int) * 4);

是一个动态内存分配过程,ptr1本身是一个指针,存储在A栈区。而*ptr1则是指向一块动态内存,存放在B堆区。

2

sizeof(num1)

num1是一个数组,此时得到的是数组的大小4*10 = 40

sizeof(char2)

char2是一个数组,内部存储着”abcd“被一个一个字符拷贝后的结果,由于字符串末尾有一个’strlen(char2)‘,所以数组长度比实际长1。最后大小为1 * (4 + 1)=5。

strlen用于统计字符串的长度,遇到’sizeof(pChar3)‘时停止同统计,所以结果为4。

pChar3是一个指针,在32位计算机中指针大小为4,64位计算机中指针大小为8。所以答案为4/8。

strlen(pChar3)

pChar3指向字符串”abcd”,长度为4,结果为4。

sizeof(ptr1)

ptr1是一个指针,在32位计算机中指针大小为4,64位计算机中指针大小为8。所以答案为4/8。

sizeof 和 strlen 区别?

sizeof()

3、 是一个运算符,而 

  • strlen() 是一个函数。sizeof() 计算的是变量或类型所占用的内存字节数,而 
  • strlen() 计算的是字符串中字符的个数。sizeof() 可以用于任何类型的数据,而 
  • strlen() 只能用于以空字符 'sizeof() 计算字符串的长度,包含末尾的 '

    说明:

    ',strlen() 计算字符串的长度,不包含字符串末尾的 '栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。'。
    ' 结尾的字符串。
    内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)堆用于程序运行时动态内存分配,堆是可以上增长的。
  • 数据段--存储全局数据和静态数据。

【C++】 CC++内存管理,【C++】 C / C++内存管理_内存空间,第1张

【C++】 CC++内存管理,【C++】 C / C++内存管理_构造函数_02,第2张

代码段--可执行的代码/只读常量。
  1. 2、C语言动态内存管理方式:malloc/calloc/realloc/free

  2. 在C语言中,动态内存管理主要是通过
  3. malloc
  4.  , 
  5. calloc
 , 

realloc , free四个函数完成的。我们简单回顾一下它们的作用与区别:malloc,calloc和realloc是C语言中用于动态内存分配的函数。

2.1、malloc函数:

作用:malloc函数用于在程序运行时动态分配指定大小的内存空间。void *malloc(size_t num, size_t size)

size

void

指针,指向分配的内存空间的起始地址。
  • 例如:
  • 使用方法:malloc函数的原型为int *ptr; ptr = (int *)malloc(10 * sizeof(int));,其中

    2.2、calloc函数

    参数表示需要分配的内存空间大小(以字节为单位)。函数返回一个作用:calloc函数用于在程序运行时动态分配指定数量、指定大小的内存空间,并将分配的内存空间初始化为零。 void *calloc(size_t num, size_t size)
  • num
size
例如:
  • int *ptr; ptr = (int *)calloc(10, sizeof(int));
  • 使用方法:calloc函数的原型为

    2.3、realloc函数:

    ,其中作用:realloc函数用于修改之前动态分配的内存空间的大小。参数表示需要分配的元素个数,void *realloc(void *ptr, size_t size)参数表示每个元素的大小(以字节为单位)。函数返回一个void指针,指向分配的内存空间的起始地址。
  • ptr
size
例如:
  • int *ptr; ptr = (int *)realloc(ptr, 20 * sizeof(int));
  • 使用方法:realloc函数的原型为

    三者的异同点:

    ,其中malloc和calloc都用于动态分配内存空间,而realloc用于调整动态分配的内存空间的大小。 参数是之前由malloc或calloc分配的内存空间的指针,malloc和calloc都返回分配的内存空间的起始地址,而realloc返回修改后的内存空间的起始地址。参数表示新的内存空间大小。函数返回一个void指针,指向修改后的内存空间的起始地址。如果返回空指针,则表示内存分配失败。
  • malloc和calloc分配的内存空间不会被初始化,而realloc可能会保留之前分配的内存内容。
realloc函数可能会将之前分配的内存空间内容复制到新的内存空间中,所以在使用realloc时要小心,以免丢失之前分配的内存中的数据。
realloc函数还可以用于分配新的内存空间,如果之前的指针是空指针,则realloc的操作相当于malloc。
  • 3、C++内存管理方式

  • new
  • delete
  • new
  • delete

new关键字用于动态分配单个对象的内存,并返回指向该对象的指针。其语法如下:

pointer = new type;pointer

在C++中,例如,以下代码分配了一个整数的内存,并将地址存储在ptr指针中: int* ptr = new int;是用于动态分配和释放内存的关键字。动态分配内存是指在程序运行时按需分配所需的内存,而不是在编译时固定分配内存。

ptr

此外,如果希望分配的内存被初始化,可以用以下语法:

其中,pointer = new type(值);是一个指针,用于存储分配的内存地址,type是要分配内存的对象类型。

比如以下代码:

int* ptr = new int(10);

此时,就是开辟了一个int的空间,并赋值为10。指向一个未初始化的整数对象。

要释放动态分配的内存,可以使用

delete

关键字。其语法如下:

int* pointer = new int;
delete pointer;

需要注意的是,使用


delete释放指针指向的内存后,该指针将不再有效,因为内存已经被释放。为了避免悬挂指针的问题,可以在释放内存后将指针设置为nullptr

,以防止后续误用。

delete ptr; ptr = nullptr;但是到此为止,好像C++的动态内存和C语言的功能没什么区别。其实C++的new和delete与C语言的malloc和free的区别体现在类上。当使用new关键字来创建对象时,会调用对象的构造函数,而malloc不会。例如:class A { public: A(int a = 0) : _a(a) { cout << "A():" << this << endl; } ~A() { cout << "~A():" << this << endl; } private: int _a; }; int main() { A* p1 = (A*)malloc(sizeof(A)); A* p2 = new A; free(p1); delete p2; return 0; }

我们定义了一个A的类,然后分别用

malloc

new

的方式开辟了内存,来存放一个A的对象。

对于p1而言,只是开辟了一个A类需要的大小,并把

void*指针转化为了A*的指针。而对于p2,不仅开辟了空间,而且调用了A类的构造函数,把

a初始化为0。所以我们在开辟类的动态内存时,最好使用new,来调用构造函数。此外,delete也会调用类的析构函数,而free

不会。

4、new[ ] & delete[ ]

4.1、new[ ]

如果需要动态分配一个数组,可以使用以下语法:pointer = new type[size];其中,

size是要分配内存的数组的大小。

例如,以下代码分配了一个包含10个整数的数组的内存,并将地址存储在

ptr

指针中:int* ptr = new int[10];此时,

ptr指向一个包含10个未初始化的整数对象的数组。此外,你还可以对这个数组进行初始化,用大括号即可:

int* ptr = new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

这样就可以将数组初始化为{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

4.2、delete[ ]

同样地,如果之前使用

new分配了一个数组,可以使用以下语法释放内存:delete[] pointer;

方括号内无需填入任何值。

例如:int* ptr = new int[10]; delete[] ptr;同样的

new[ ]

 & 

delete[ ]

如果作用与于类,那么会调用相应的构造函数与析构函数。

4.3、定位new

那么假如我们用malloc开辟了一个类的空间,还能不能对这个类初始化,调用其构造函数?是可以的,这就需要定位new了。在C++中,定位new是一种特殊的new表达式,它允许我们在指定的内存位置上创建对象。通常情况下,new表达式会自动分配内存,并在该内存上创建对象。但是,有时我们希望将对象放置在已经分配的内存中,这就需要使用定位new。定位new的语法如下:

new (指针) 类型(参数列表);

其中,指针是一个指向已经分配的内存的指针,类型是要创建的对象的类型,参数列表是对象构造函数的参数。

例如:

class MyClass { public: int value; MyClass(int v) : value(v) { std::cout << "构造函数被调用了" << value << std::endl; } }; int main() { // 分配内存 void* memory = malloc(sizeof( MyClass)); // 在已分配的内存上创建对象 MyClass* obj = new (memory) MyClass(10); //销毁对象 obj->~MyClass(); return 0; }

在上面的示例中,首先我们使用

malloc

分配了足够的内存以容纳一个

MyClass

对象。然后,我们使用定位new在已分配的内存中创建了一个

MyClass对象,并传递了一个参数值10给构造函数。我们可以看到构造函数被调用,并输出了相应的消息。需要注意的是,使用定位new创建的对象必须手动调用析构函数进行销毁,并手动释放相应的内存。定位new在一些特定的情况下非常有用,例如在实现自定义的内存管理时,或者在某些嵌入式系统中,需要将对象放置在特定的内存地址上。但是在一般的编程中,几乎用不上。

4.5、new & delete原理

其实new和delete本质上还是malloc和free,但是C++在两者基础上做了很多优化,最后才得到的new和delete,接下来我将对new和delete进行拆解,带大家看清两者的原理。对于new来说,其要完成的工作有:

开辟指定大小的空间

如果开辟空间失败,抛出异常(对malloc而言是返回空指针)

如果开辟的空间用于存放对象,那么调用对应的析构函数

抛出异常是C++相比于C语言特有的步骤,可以简单理解为报错。那么C++要如何检测开辟内存失败?

malloc

  1. 开辟内存失败就会返回空指针,所以我们可以通过
  2. malloc
  3. 的返回值确定是否要抛出异常。所以抛出异常这个步骤也与

malloc

紧密关联,于是C++将第一步与第二步封装成了一个函数operator new ,它可以同时完成内存开辟和抛出异常,而两者都基于malloc实现。而我们平常使用的new就是operator new函数+构造函数。这也就是new的底层原理:将malloc进行封装,完成内存开辟与抛出异常,再额外调用构造函数,完成对象的初始化


delete也将

free进行了一个封装,构成了一个operator delete

函数,其完成内存的释放,此外还会调用析构函数,完成对象调用的资源的释放。不过要注意,delete是先调用析构函数释放对象调用的资源,再调用operator delete

完成内存释放。

总结

new的原理调用operator new


函数申请空间

在申请的空间上执行构造函数,完成对象的构造delete

  1. 的原理在空间上执行析构函数,完成对象中资源的清理工作调用
  2. operator delete

函数释放对象的空间new[ ]

  1. 的原理
  2. 调用operator new[]函数,在

operator new[]中实际调用

  1. operator new函数完成N个对象空间的申请在申请的空间上执行N次构造函数delete[ ]的原理在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理调用
  2. operator delete[]

释放空间,实际在operator delete[]

  1. 中调用
  2. operator delete来释放空间

    5、malloc / free 与 new / delete对比

    相同点:

    都是从堆上申请空间,并且需要用户手动释放。

不同点:

malloc

free是函数,

new
  • delete是操作符malloc申请的空间不会初始化,new可以初始化malloc
  • 申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可malloc
  • 的返回值为void*, 在使用时必须强转,new
  • 不需要,因为new后跟的是空间的类型malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new
  • 不需要,但是new需要捕获异常申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

https://www.xamrdz.com/lan/52b1967403.html

相关文章: