Bootstrap

C/C++内存和内存管理

一.C/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 在哪里? __C__   staticGlobalVar 在哪里? __C__
staticVar 在哪里? __C__   localVar 在哪里? __A__
num1 在哪里? __A__
char2 在哪里? __A__   * char2 在哪里? _A__
pChar3 在哪里? __A__       * pChar3 在哪里? __D__
ptr1 在哪里? __A__         * ptr1 在哪里? __B__
2. 填空题:
sizeof ( num1 ) = __40__ ;  
sizeof ( char2 ) = __5__ ;       strlen ( char2 ) = __4__ ;
sizeof ( pChar3 ) = __4/8__ ;     strlen ( pChar3 ) = __4__ ;
sizeof ( ptr1 ) = __4/8__ ;
3. sizeof strlen 区别?
1. **适用对象**
`size` 适用于标准库容器和对象,如 `std::vector`, `std::array`, `std::string` 等。 
`strlen` 仅适用于C风格字符串(以 `\0` 结尾的字符数组)。
2. **函数类型**:
`size` 是成员函数,需要通过对象调用。 
`strlen` 是独立函数,直接调用即可。
3. **性能**:
`size` 通常是一个常数时间操作(O(1)),因为容器通常会维护其大小信息。 
`strlen` 是一个线性时间操作(O(n)),因为它需要遍历整个字符串直到遇到空字符 `\0`。
【说明】
此栈和堆并不是数据结构里面的栈和堆,而是内存中的栈和堆。
栈(Stack)和堆(Heap)是计算机科学中两种重要的内存管理方式,它们在程序运行时用于存储数据。尽管它们都是内存区域,但它们的工作方式、用途和特点有很大的不同。
以下是栈和堆的主要区别:
1. 分配方式 :
 **栈**:由编译器自动分配和释放。当一个函数被调用时,该函数的局部变量会被分配到栈上,当函数返回时,这些变量会被自动释放。
 **堆**:由程序员手动分配和释放。使用 `malloc` 或 `new` 等函数在堆上分配内存,使用 `free` 或 `delete` 等函数释放内存。
2. 存储内容 :
**栈**:主要用于存储函数的局部变量、参数和返回地址等。栈上的数据通常是临时的,生命周期与函数的调用和返回同步。
 **堆**:主要用于存储动态分配的数据,如动态数组、对象等。堆上的数据可以被多个函数共享,生命周期由程序员控制。
3. 访问速度 :
 **栈**:访问速度快。因为栈的内存分配和释放是线性的,可以通过简单的指针操作实现,因此效率很高。
 **堆**:访问速度相对较慢。堆的内存分配和释放需要更复杂的管理机制,如查找空闲内存块、分配合并等,因此效率较低。
4. 内存大小 :
**栈**:大小有限,通常由系统设定。如果栈空间不足,可能会导致栈溢出(Stack Overflow)。
**堆**:大小相对较大,受系统可用内存的限制。堆上的内存分配更为灵活,但管理也更为复杂。
5. 数据结构 :
**栈**:后进先出(LIFO)的数据结构。栈顶指针指向当前栈的顶部,每次入栈或出栈都只操作栈顶元素。
**堆**:没有固定的结构,内存块可以随意分配和释放。堆上的内存管理更为复杂,需要维护一个内存分配表来记录已分配的内存块。 
6. 使用场景:
**栈**:适用于存储局部变量、函数参数和返回地址等短期使用的数据。
**堆**:适用于存储需要长期存在的数据,如动态创建的对象、大数组等。
【总结】:
**栈**:自动管理,访问速度快,适合短期使用的数据。
**堆**:手动管理,访问速度较慢,适合长期使用的数据。
简单的来说:
1. 又叫堆栈 -- 非静态局部变量 / 函数参数 / 返回值等等,栈是向下增长的。
2. 内存映射段 是高效的 I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux 课程如果没学到这块,现在只需要了解一下)
3. 用于程序运行时动态内存分配,堆是可以上增长的。
4. 数据段 -- 存储全局数据和静态数据。
5. 代码段 -- 可执行的代码 / 只读常量。

二.C语言中动态内存管理方式

malloc、calloc、realloc、free。

void Test ()
{
    int* p1 = (int*) malloc(sizeof(int));
    free(p1);

    // 1.malloc/calloc/realloc的区别是什么?
    int* p2 = (int*)calloc(4, sizeof (int));
    int* p3 = (int*)realloc(p2, sizeof(int)*10);
    // 这里需要free(p2)吗?
    free(p3 );
}

代码中的p2是不用free的:

在 `realloc` 成功后,将 `p2` 更新为 `p3`,如果 `realloc` 成功分配了新的内存,它会返回一个新的指针(`p3`),并且会自动释放旧的内存(`p2`)。 如果 `realloc` 失败(例如,内存不足),它会返回 `NULL`,并且原始内存仍然有效,不会被释放。但是free(p3)就是已经释放了p2。在使用 `realloc` 时,不需要显式地释放原来的指针 `p2`,因为 `realloc` 会自动处理这一点。但是,你需要小心处理 `realloc` 返回的指针,以确保不会出现内存泄漏或其他错误。

 malloc/calloc/realloc的区别

**`malloc`**:分配指定大小的内存,内存未初始化。

**`calloc`**:分配指定数量的元素,每个元素具有指定的大小,并将内存初始化为零。

**`realloc`**:重新调整已分配内存块的大小,可以增大或减小内存块的大小。

malloc/freenew/delete的区别

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

三.C++中动态内存管理方式

C中的方式在C++中可以继续使用,但是分情况,有的不行,也会比较麻烦。所以C++中有自己的内存管理方式:new和delete操作符进行动态内存管理。

void Test()
{
      // 动态申请一个int类型的空间
      int* ptr4 = new int;
  
      // 动态申请一个int类型的空间并初始化为10
      int* ptr5 = new int(10);
  
      // 动态申请10个int类型的空间
      int* ptr6 = new int[3];
      delete ptr4;
      delete ptr5;
      delete[] ptr6;
}

注意:申请和释放单个元素的空间,使用newdelete操作符,申请和释放连续的空间,使用

new[]delete[],注意:匹配起来使用。

new和delete 的操作:

class A
{
public:
     A(int a = 0)
         : _a(a)
     {
         cout << "A():" << this << endl;
     }
     ~A()
     {
         cout << "~A():" << this << endl;
     }
private:
     int _a;
};
int main()
{
     // new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
     //还会调用构造函数和析构函数
     A* p1 = (A*)malloc(sizeof(A));
     A* p2 = new A(1);
     free(p1);
     delete p2;
     // 内置类型是几乎是一样的
     int* p3 = (int*)malloc(sizeof(int)); // C
     int* p4 = new int;
     free(p3);
     delete p4;
     A* p5 = (A*)malloc(sizeof(A)*10);
     A* p6 = new A[10];
     free(p5);
     delete[] p6;
     return 0;
}
注意:在申请自定义类型的空间时, new 会调用构造函数, delete 会调用析构函数,而 malloc
free 不会

四.opertor new 和 operator delete 函数(重点)

new delete 是用户进行 动态内存申请和释放的操作符 operator new operator delete
系统提供的 全局函数 new 在底层调用 operator new 全局函数来申请空间, delete 在底层通过
operator delete 全局函数来释放空间。
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空               间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
  if (_callnewh(size) == 0)
     {
         // report no memory
         // 如果申请内存失败了,这里会抛出bad_alloc 类型异常
         static const std::bad_alloc nomem;
         _RAISE(nomem);
     }
return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void *pUserData)
{
     _CrtMemBlockHeader * pHead;
     RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
     if (pUserData == NULL)
         return;
     _mlock(_HEAP_LOCK);  /* block other threads */
     __TRY
         /* get a pointer to memory block header */
         pHead = pHdr(pUserData);
          /* verify block type */
         _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
         _free_dbg( pUserData, pHead->nBlockUse );
     __FINALLY
         _munlock(_HEAP_LOCK);  /* release other threads */
     __END_TRY_FINALLY
     return;
}
/*
free的实现
*/
#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)
通过上述两个全局函数的实现知道, operator new 实际也是通过 malloc 来申请空间 ,如果
malloc 申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施
就继续申请,否则就抛异常。 operator delete 最终是通过 free 来释放空间的

五.new 和 delete 的实现原理

1.内置类型:

如果申请的是内置类型的空间, new malloc delete free 基本类似,不同的地方是:
new/delete 申请和释放的是单个元素的空间, new[] delete[] 申请的是连续空间,而且 new 在申
请空间失败时会抛异常, malloc 会返回 NULL
2.自定义类型:
 
new 的原理
        1. 调用operator new 函数申请空间
        2. 在申请的空间上执行构造函数,完成对象的构造
delete 的原理
        1. 在空间上执行析构函数,完成对象中资源的清理工作
        2. 调用 operator delete 函数释放对象的空间
new T[N] 的原理
        1. 调用 operator new[] 函数,在 operator new[] 中实际调用 operator new 函数完成 N 个对象空间的申请
        2. 在申请的空间上执行 N 次构造函数
delete[] 的原理
        1. 在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理
        2. 调用 operator delete[] 释放空间,实际在 operator delete[] 中调用 operator delete 来释放空间

六.内存泄漏问题

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

;