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

C++内存分布

C++内存分布通常包含以下几个部分:

  1. 栈(Stack):用于存储局部变量以及函数参数。当声明一个局部变量时,它会被存放在栈中。栈是自上而下的数据结构,进入作用域时分配内存,离开作用域时释放内存。栈的分配和回收速度非常快,但是大小有限。

  2. 堆(Heap):用于动态内存分配,由程序员分配释放。如果需要在函数调用结束后仍保留数据或者需要动态分配内存大小,就可以使用堆。使用堆空间可以创建大小动态变化的数据结构,如链表和树。但是,管理堆内存(使用newdelete)需要谨慎,以避免内存泄漏和碎片化。

  3. 全局/静态存储区:存储全局变量、静态变量和常量(静态主导)。这部分内存在程序启动时分配,在程序结束时释放。全局变量和静态变量的生命周期贯穿整个程序运行期间,而局部静态变量的生命周期虽然也是整个程序,但其作用域局限于声明它的函数内。

  4. 代码区:存放程序的二进制代码,即编译后的机器指令。这部分内存是只读的,用来存放执行的代码。

  5. 常量区:存储常量字符串和其他常量数据。这部分数据通常也是只读的,位于程序的数据段中。


/*内存分布*/
using namespace std;
#include 
int globalvar = 1;//---全局/静态存储区
static int staticGlobalVar = 1;//---全局/静态存储区
void Test() {
    static int staticVar = 1;//---全局/静态存储区
    int localVar = 1;//---栈(Stack)

    int num1[10] = {1, 2, 3, 4};//---栈(Stack)
    char char2[] = "abcd";//---栈(Stack)
    const char* pchar3 = "abcd";//栈(Stack)---常量区
    int* ptr1 = (int*)malloc(sizeof(int) * 4);//栈(Stack)---堆(Heap)
    int* ptr2 = (int*)calloc(4, sizeof(int));//栈(Stack)---堆(Heap)
    int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);//栈(Stack)---堆(Heap)
    free(ptr1);//---栈(Stack)
    free(ptr3);//---栈(Stack)
 }

sizeof和strlen的区别

sizeof运算符

  1. sizeof是一个编译时运算符,用于计算其操作数的大小(以字节为单位)。这个大小是在编译时确定的,不是在运行时。

  2. 对于数据类型(如int、float等),sizeof返回该类型所占的字节数。

  3. 对于数组,sizeof返回整个数组占用的内存大小,包括所有元素。

  4. 对于指针,sizeof返回指针本身的大小,而不是指针指向的内存大小。

  5. sizeof可以用于任何数据类型,包括基本类型、数组、指针、结构体等。

strlen函数

  1. strlen是一个标准库函数,用于计算C风格字符串(即以null终止的字符数组)的长度,不包括终止的null字符。

  2. strlen的参数是一个指向字符数组(字符串)的指针。它通过遍历字符串直到遇到null终止符来计算字符串的实际长度。

  3. strlen的返回值是一个size_t类型,表示字符串的长度(不包括null终止符)。

  4. strlen在运行时计算字符串的长度,因此其返回值依赖于字符串的实际内容。


/*sizeof与strlen区别*/
#include
#include
using namespace std;
int main(){
    char str[] = "Hello";
    char* pstr = "Hello";
    cout<<"sizeof(str):"<

【六】【C++】C++内存管理_第1张图片

使用sizeof

  sizeof(str)将返回6,因为str数组包含5个字符加上一个null终止符,每个字符占用1字节。

  sizeof(pstr)将返回指针的大小,这个大小依赖于平台(例如,在64位系统上通常是8字节)。

使用strlen

  strlen(str)将返回5,因为str字符串的长度是5(不包括null终止符)。

  如果pstr指向一个以null终止的字符串,strlen(pstr)也会返回该字符串的长度(不包括null终止符)。

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

在C语言中,动态内存管理是通过几个标准库函数实现的,主要包括malloccallocreallocfree。这些函数提供了在堆(heap)上动态分配、重新分配和释放内存的能力,使得程序可以根据需要在运行时管理内存。

malloc函数

原型:void* malloc(size_t size);

用途:分配size字节的未初始化内存。如果分配成功,返回指向分配内存的指针;如果分配失败,返回NULL

特点:分配的内存区域不会被自动初始化,其内容是未定义的。


/*malloc*/
#include 
#include 

int main() {
    int *ptr = (int*)malloc(sizeof(int) * 4); // 分配4个整数的空间
    if (ptr == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }
    
     for (int i = 0; i < 4; i++) {
        ptr[i] = i;
    }
    
     for (int i = 0; i < 4; i++) {
        printf("%d ", ptr[i]);
    }
    printf("\n");
    
     free(ptr); // 释放内存
    return 0;
 }

【六】【C++】C++内存管理_第2张图片

calloc函数

原型:void* calloc(size_t num, size_t size);

用途:分配足够空间以容纳num个大小为size的对象,并将分配的内存初始化为零。如果分配成功,返回指向分配内存的指针;如果分配失败,返回NULL

特点:与malloc不同,calloc分配的内存区域会被自动初始化为零。


/*calloc*/
#include 
#include 

int main() {
    int *ptr = (int*)calloc(4, sizeof(int)); // 分配并初始化4个整数的空间
    if (ptr == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }
    
     for (int i = 0; i < 4; i++) {
        printf("%d ", ptr[i]); // 默认初始化为0
    }
    printf("\n");
    
     free(ptr); // 释放内存
    return 0;
 }

【六】【C++】C++内存管理_第3张图片

realloc函数

原型:void* realloc(void* ptr, size_t size);

用途:更改之前调用malloccalloc分配的内存块的大小。ptr是指向旧内存块的指针,size是新内存块的大小。如果分配成功,返回指向重新分配内存的指针;如果分配失败,返回NULL,且原指针ptr仍然有效。

特点:如果新大小大于原大小,新增的内存不会被初始化;如果新大小小于原大小,超出的部分会被丢弃。


#include 
#include 

int main() {
    int *ptr = (int*)malloc(sizeof(int) * 2); // 最初分配2个整数的空间
    if (ptr == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }

    for (int i = 0; i < 2; i++) {
        ptr[i] = i + 1;
    }

    ptr = (int*)realloc(ptr, sizeof(int) * 4); // 扩展到4个整数的空间
    if (ptr == NULL) {
        printf("Memory reallocation failed\n");
        return 1;
    }

    for (int i = 2; i < 4; i++) {
        ptr[i] = i + 1;
    }

    for (int i = 0; i < 4; i++) {
        printf("%d ", ptr[i]);
    }
    printf("\n");

    free(ptr); // 释放内存
    return 0;
}

【六】【C++】C++内存管理_第4张图片

free函数

原型:void free(void* ptr);

用途:释放之前通过malloccallocrealloc分配的内存。ptr是指向要释放的内存块的指针。

特点:一旦内存被释放,该指针ptr就不再是一个有效的内存引用。继续使用这样的指针是未定义行为,通常称为悬挂指针。

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

在C++中,动态内存管理除了可以使用C语言风格的malloccallocreallocfree函数之外,还引入了更安全和更直观的操作方式,主要通过使用newdelete操作符来实现。

newdelete操作符

new操作符

用于分配内存。它会自动计算需要分配的内存大小,并调用对象的构造函数(如果有的话)。

delete操作符

用于释放new操作符分配的内存,并调用对象的析构函数(如果有的话)。

new/delete操作内置类型


/*new/delete操作内置类型*/
#include 
using namespace std;
void Test() {
    int* ptr1 = new int;
    int* ptr2 = new int(10);
    int* ptr3 = new int[3];


    delete ptr1;
    delete ptr2;
    delete[] ptr3;
 }

new/delete操作自定义类型


/*new/delete操作自定义类型*/
#include 
using namespace std;
class A {
    private:
        int _a;
    public:
        A(int a = 0)
            : _a(a) {
            cout << "A(int a=0)" << endl;
        }
        ~A() {
            cout << "~A()" << endl;
        }
 };
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));
    int* p4 = new int;
    free(p3);
    delete p4;

    A* p5 = (A*)malloc(sizeof(A) * 10);
    A* p6 = new A[10];
    free(p5);
    delete[] p6;
 }

operator new与operator delete底层代码


/*operator new与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表达式(placement-new)

定位new表达式(Placement new)是C++中一个非常有用的特性,它允许你在已分配的内存上构造对象。这种技术特别适合于需要直接在特定内存位置构造对象的场景,例如在内存池、共享内存或嵌入式系统中使用。定位new的基本思想是重用已有的内存,而不是通过标准的new表达式请求新的内存分配。


#include 
#include  // 对于定位new必须包含

class MyClass {
public:
    MyClass(int x) {
        std::cout << "MyClass constructed with value " << x << std::endl;
    }
};

int main() {
    char buffer[sizeof(MyClass)]; // 分配足够的内存来存储MyClass对象
    MyClass* pMyClass = new(buffer) MyClass(10); // 在buffer指向的内存上构造对象

    // 使用pMyClass做一些事情...
    
    pMyClass->~MyClass(); // 显式调用析构函数来销毁对象

    // 注意:不应该使用delete来释放pMyClass,因为内存不是通过new分配的
}
  1. 使用定位new时,你需要确保分配的内存足够大且适当对齐,以存放指定类型的对象。

  2. 由于定位new不会分配内存,因此你不应该使用delete来释放对象;相反,你需要显式调用对象的析构函数来销毁它,如果需要的话,手动释放内存。

malloc/free和new/delete的区别

malloc/freenew/delete是C++中用于内存管理的两套机制,它们在功能上有一定的重叠,但也存在一些关键的区别。理解这些区别对于编写高效、可靠的C++代码非常重要。

基本区别

  1. 来源和兼容性:

    mallocfree是C语言中的函数,用于动态内存分配和释放。它们在C++中仍然可用,提供了与C代码的兼容性。
    newdelete是C++引入的操作符,提供了面向对象的内存管理方式。
  2. 构造函数和析构函数调用:

    malloc仅分配内存,不调用构造函数;free仅释放内存,不调用析构函数。  
    new在分配内存的同时调用对象的构造函数;delete在释放内存前调用对象的析构函数。
  3. 错误处理:

     malloc在无法分配内存时返回nullptr。  
    new在默认情况下会抛出std::bad_alloc异常,除非使用了nothrow版本,此时它会返回nullptr
  4. 类型安全:

     malloc返回void*类型,需要显式转换为目标类型指针。  
    new直接返回正确的类型指针,提供了类型安全。
  5. 内存对齐:

     malloc提供的内存对齐可能不适合所有类型的对象。  
    new保证了分配的内存满足对象对齐要求。
  6. 分配大小的计算:

    使用malloc时,必须手动计算分配的内存大小。  
    new自动计算所需的内存大小,基于对象的类型。
  7. 重载:

     mallocfree不能被重载,它们是标准库函数。  
    newdelete可以被重载,允许自定义内存分配和释放行为。

什么是内存泄漏,内存泄漏的危害

什么是内存泄漏:内存泄漏是程序中的一种资源管理错误,发生于当程序分配的内存没有被正确释放回操作系统或可用内存池,即便它已经不再被使用或无法被访问。内存泄漏是动态内存管理中常见的问题,尤其在使用如C和C++这类需要手动内存管理的编程语言时。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,会导致资源浪费、性能下降、系统稳定性降低,最终可能引起程序或系统崩溃,增加维护成本。


/*内存泄漏示例*/
void MemoryLeaks()
 {
// 1.内存申请了忘记释放
    int* p1 = (int*)malloc(sizeof(int));
    int* p2 = new int;
// 2.异常安全问题
    int* p3 = new int[10];
    Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
    delete[] p3;
 }

内存泄漏的分类

C/C++程序中一般我们关心两种方面的内存泄漏:

堆内存泄漏(Heap leak)

堆内存指的是程序执行中依据需要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

系统资源泄漏

指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

你可能感兴趣的:(C++,c++)