精华内容
下载资源
问答
  • 动态内存分配 (详解版)

    千次阅读 多人点赞 2021-03-18 20:31:27
    动态内存分配 (详解版) malloc和free C++语言允许使用C语言标准库函数中malloc和free申请和释放内存,保留这两个函数主要有以下3点考虑: C++程序经常要调用写好的C函数,而在C语言中,只能使用malloc和free; ...

    malloc和free

    C++语言允许使用C语言标准库函数中mallocfree申请和释放内存,保留这两个函数主要有以下3点考虑:

    1. C++程序经常要调用写好的C函数,而在C语言中,只能使用mallocfree
    2. 如果C++程序要允许在C语言环境下,必须使用mallocfree
    3. newdelete的功能是通过调用mallocfree来实现的
    4. newdelete是C++运算符,newdelete是C标准库函数。

    C函数库提供了mallocfree两个函数,分别用于执行动态内存分配和释放。它们都在头文件stdlib.h中声明。原型如下:

    void *malloc(size_t size);       //无符号整型,unsigned int size
    void free(void *pointer);        //pointer是指向所申请内存块的指针。编译器可以完成由其他类型指针向void型指针的转化,因此可直接使用free(指针)就可实现内存释放
    

    下列语句用于申请一段长度为len、数据类型为short的动态内存:

    short* p=(short*) malloc (len * sizeof(short)); //由于malloc返回类型是void*,用其返回值对其他类型指针赋值时,必须使用显示转换。
    //同时,malloc参数是个无符号整数,其仅仅关心申请字节的大小,并不管申请的内存块中存储的数据类型。
    //因此,申请内存的长度须由程序员通过“长度*sizeof(类型)”方式。
    

    malloc的参数就是需要分配的内存字节数。如果内存池中的可用内存可用满足这个需求,malloc就返回一个指向被分配的内存块起始位置的指针。malloc所分配的是一个连续内存,因此实际分配的内存有可能比请求的稍微多一些,这个行为由编译器定义。

    如果内存池是空的,或者它的可用内存无法满足请求,malloc函数向操作系统请求,要求得到更多的内存,并在这块新内存上执行分配任务。

    如果操作系统无法向malloc提供更多的内存,malloc就返回一个NULL指针。

    因此,对每个从malloc返回的指针都进行检查,确保它并非NULL.

    free的参数要么是NULL,要么是一个先前从malloc/calloc/realloc返回的值。向free传递一个NULL参数不会产生任何影响。

    这些函数维护一个可用内存池,当一个程序另外需要一些内存时,就调用malloc函数,malloc从内存池中提取一块合适的内存,并向该程序返回一个指向这块内存的指针。这块内存此时并没有以任何方式进行初始化。如果对这块内存进行初始化非常重要,要么自己动手初始化,要么使用calloc函数。

    当一块以前分配的内存不再使用时,程序调用free函数把它归还给内存池供以后之需。

    #include<iostream>
    using namespace std;
    int main()
    {
        int *p=(int*) malloc(sizeof(int)*5); //使用malloc申请一块动态内存
        cout<<"请输入5个整数:"<<endl;
        for(int i=0;i<5;i++)
        {
            cin>>*(p+i);                 //循环输入5个整数,与*p[i]一样
        }
        cout<<"您输入的第3个数是:"<<p[2]<<endl;
        free(p);                        //释放所申请的动态内存
        return 0;
    }
    

    new和delete

    C++中利用专门的运算符“创建(new)”和“撤销(delete)”对内存进行动态分配,这样便可在程序运行时申请一块未命名的内存用来存储变量或者更为复杂的数据结构,并把该内存的首地址记录下来,以备将来访问。

    使用newdelete申请和释放动态内存时,内存 的长度是由编译器自动计算的。

    1.使用new动态分配内存

    new是一个单目运算符,返回值为指向操作数类型的指针。基本格式如下:

    // 类型名 * 指针变量名= new 类型名
    int *pNum=new int;
    

    new操作符可根据这个类型名自动计算要分配的存储空间的大小。上述代码会在运行时为一个int型数值分配内存,声明了指向int型的指针pNum,并用动态申请内存的首地址为pNum进行初始化。因此,用指针pNum可以访问这块内存区域。

    申请内存的同时可对该区域进行初始化,对基本的变量类型,下列语句是合法的:

    //动态申请了大小为int型的内存,将这块区域初始化为8,把该区域的首地址赋值给pNum。
    int *pNum=new int(8); 
    
    2.使用delete释放动态申请的内存
    delete 指针;
    

    指针指向使用new动态申请的内存块,delete指令会释放动态申请的内存块,但不会删除指针本身,还可以将指针重新指向另一块内存区域。

    //delete语句不能释放声明变量获得的内存
    int x=3;
    int* p=&x;
    delete p;    //错误
    
    3.使用new申请动态数组

    通过new[]命令动态创建数组,格式如下:

    // 类型名 * 指针变量名= new 类型名[元素个数]
    int i=5;
    int *p=new int[i];  // 合法
    

    上述语句通知编译器动态开辟足以存储“元素个数”个类型为类型名的元素的连续内存空间(数组),并声明“指针变量名”,指向数组的第一个元素。

    和通过声明建立数组不同。使用new申请动态数组时,元素个数可以是变量。声明建立数组时,为了避免数组越界,一般都将数组的维数尽量设大一点,因此造成了内存浪费。

    注意:new无法对动态申请的数组存储区进行初始化。

    对于动态申请的数组,使用完毕后,使用delete[]命令将内存释放,格式如下:

    delete[] 指针;
    

    方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。

    因此,一定要注意newdelete的配对,new&delete用于为一个实体分配内存,而new[] & delete[]用于为数组分配内存。

    #include<iostream>
    using namespace std;
    int main()
    {
        int num=0;                    //定义整型变量代表数组个数
        cout<<"请输入数组个数:"<<endl;
        cin>>num;                     //获取数组个数
        cout<<"请依次输入"<<num<<"个整数(用空格隔开):"<<endl;
        int* p=new int[num];          //申请一块可存放num个int型数据的动态内存,将首地址赋值给指针p;
        for(int i=0;i<num;i++)
        	cin>>p[i];                //for循环结构为动态数组中的元素赋值
        for(i=0;i<num;i++)
            cout<<"第"<<i<<"个数为:"<<p[i]<<endl;  //对数组元素依次输出
        delete [] p;                              //释放动态内存
        return 0; 
    }
    
    4.不使用或释放已经释放的内存块

    在使用delete释放内存时,delete后的指针并不要求一定是用new命令赋值的那个指针,编译器关心的是内存块的地址,而不是用哪个指针来释放。

    #include<iostream>
    using namespace std;
    int main()
    {
        int *p1=new int(8);   //申请一个int型大小的动态内存,初始化为8
        int *p2=p1;           //指针间的赋值
        delete p2;            //释放p2所指的动态内存,实际上已经释放了p1申请的内存;
        //此时对*p1的访问没有意义,因为p1所指向的内存已经被释放,所以程序输出值并不是8,而是一个随机值
        cout<<*p1<<endl; 
        //释放p1所指的动态内存;相当于对已经释放的内存再次释放,此时系统报错,出现不可预料的错误。
        delete p1; 
        return 0; 
    }
    

    注意:释放NULL指针(即空指针)是没有问题的。

    在使用动态申请的内存块时,应首先判断申请是否成功(指针是否为NULL),代码如下:

    char *p=new char[10];
    if(p!=null)
        //执行操作
    else
        //内存申请失败处理
    

    freedelete一个指针后,该指针所指向的动态内存被释放,但指针的值并不发生变化,常称此时的指针为“野指针”。

    通常,在内存释放后,将指针赋值为NULL,这样便不会再次释放已经释放了的内存,也可以通过if(指针!=null)进行防错。

    常见的动态内存错误

    1)对NULL指针进行解引用操作;

    2)对分配的内存进行操作时越过边界;

    3)释放并非动态分配的内存可能导致程序立即终止或在晚些时候终止;

    4)试图释放一块动态分配的内存的一部分;

    5)一块动态内存被释放之后被继续使用。(不要访问已经被free函数释放了的内存)

    内存泄漏

    当动态分配的内存不再需要时,应该被释放,这样它以后可以被重新分配使用。分配内存但在使用完毕后不释放将引起内存泄漏。即 :内存泄漏是指程序中已动态分配的堆内存由于某种原因未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统奔溃等严重后果。

    在那些所有执行程序共享一个通用内存池的操作系统中,内存泄漏将一点点地榨干可用内存,最终将其一无所有。

    野指针

    前面提到,指针消亡,并不意味着其所指向的内存会被自动释放,同样,释放动态内存,并不意味着指针会消亡,也不意味着指针的值会改变,如下所示:

    //假设p是代码块内声明的局部指针变量,在代码块执行完毕退出时,p自动消亡,但p指向的大小为8、类型为int的内存空间不会释放掉。
    //代码退出后,程序无法再通过指针p释放所申请的动态内存,这块内存已经“泄露——————内存泄漏
    int *p=new int[8];  
    ...
    //运行以后,指针p非但不会消亡,其值还会保持不变,并不会变为null。此时使用if(指针!=null)进行处理也无法起到防错作用。
    delete [] p;        
    

    解决方法:指针被free或者delete后,一定要置为null,没有置为null的指针常称为”野指针“。

    下列两种情况也可以称为**”野指针“**:

    1)未初始化指针

    任何指针变量刚创建时,其内容是随机的,在内存中乱指一通。因此,使用指针前,一定要对其初始化。在声明的同时初始化或赋值,使其指向合法的内存。对于无处可指的指针变量,也要将其赋值/初始化为null,也就是空指针。

    2)指向临时变量的指针

    当前代码块执行完毕后,代码块中声明的临时变量(包括函数调用时的参数)都自动消亡,其占用的内存空间(栈内存)也被内存管理器收回。

    此时,指向这些临时变量的指针便没有意义,使用这些指针会给程序造成不可预料的结果。

    内存溢出

    内存溢出指:程序在申请内存时,没有足够的内存供申请者使用,或者说,给了一块存储int类型数据的存储空间,但是却存储long类型的数据,就会导致内存不够用,报错,即出现内存溢出的错误。

    就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误。

    内存溢出原因:

    1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;

    2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;

    3.代码中存在死循环或循环产生过多重复的对象实体;

    4.使用的第三方软件中的BUG;

    5.启动参数内存值设定的过小

    内存溢出的解决方案:
    第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加)

    第二步,检查错误日志,查看“Out Of Memory”错误前是否有其它异常或错误。

    第三步,对代码进行排查和分析,找出可能发生内存溢出的位置。

    内存泄漏和内存溢出的联系

    1:内存泄漏的堆积最终会导致内存溢出

    2:内存溢出:就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误

    3:内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。

    展开全文
  • 动态内存分配(malloc)详解

    万次阅读 多人点赞 2020-09-11 10:18:05
    malloc()找到可用内存中一个大小适合的块。 内存是匿名的; 也就是说,malloc()分配了内存,但没有为它指定名字。 然而,它却可以返回那块内存第一个字节的地址。 因此,可以把那个地址赋值给一个指针变量,并...

    malloc

    • malloc()找到可用内存中一个大小适合的块。
      内存是匿名的;

      也就是说,malloc()分配了内存,但没有为它指定名字。
      然而,它却可以返回那块内存第一个字节的地址
      因此,可以把那个地址赋值给一个指针变量,并使用该指针来访问那块内存。
      因为char代表一个字节,所以传统上曾将malloc()定义为指向char的指针类型

      然而,ANSIC标准使用了一个新类型:指向void的指针。这一类型被用作“通用指针”。
      函数malloc()可用来返回数组指针、结构指针等等,因此一般需要把返回值的类型指派为适当的类型。

      在ANSIC中,为了程序清晰应对指针进行类型指派,但将void 指针值赋值给其他类型的指针并不构成类型冲突。
      如果malloc()找不到所需的空间,它将返回空指针
      我们使用malloc()来创建一个 数组。可以在程序运行时使用malloc()请求一个存储块,另外还需要一个指针来存放该块在内存中的位置。

      例如,如下代码:
    double * ptd;
    ptd = (double * ) malloc (30 * sizeof(double));
    
    • 这段代码请求30个double类型值的空间,并且把ptd指向该空间所在位置。

      注意:ptd是作为指向一个double类型值的指针声明的,而不是指向30个double类型值的数据块的指针。

      记住:数组的名字是它第一个元素的地址
      因此,如果令ptd指向一个内存块的第一个元素,就可以像使用数组名一样使用它
      也就是说,可以使用表达式ptd[0]来访问内存块的第一个元素,pd[1]来访问第二个元素,依此类推。
      正如前面所学,可以在指针符号中使用数组名,也可以在数组符号中使用指针。

    • 现在,创建一个数组有三种方法
      1.声明一个数组,声明时用常量表达式指定数组维数,然后可以用数组名访问数组元素。
      2.声明一个变长数组,声明时用变量表达式指定数组维数,然后用数组名来访问数组元素(这是C99的一个特性)。
      3.声明一个指针,调用malloc(),然后使用该指针来访问数组元素。

    使用第二种或第三种方法可以做一些用普通的数组声明做不到的事:

    创建一个动态数组(dynamic array),即一个在程序运行时才分配内存并可在程序运行时选择大小的数组。

    例如,假定n是一个整数量。在C99之前,不能这样做:

    double item[n]/*如果n是一个变量,C99之前不允许这样做*/
    

    然而,即使在C99之前的编译器中,也可以这样做:

    ptd =double*)malloc(n*sizeofdouble));/*可以*/
    

    这行得通,而且正如您将看到的那样,这样做比使用一个变长数组更灵活。

    一般地,对应每个malloc()调用,应该调用一次free()。
    函数free()的参数是先前malloc()返问的地址,它释放先前分配的内存。

    这样,所分配内存的持续时间从调用malloc()分配内存开始,到调用free()释放内存以供再使用为止

    • 设想malloc()和free()管理着一个内存池。
      每次调用malloc()分配内存给程序使用,每次调用free()将内存归还到池中,使内存可被再次使用。

    注意:

    1. free()的参数应是一指针,指向由malloc()分配的内存块;
    2. 其他方式(例如声明一个数组)分配的内存是不能使用free()去释放的。

    在头文件stdlib.h中有malloc()和free()的原型。(我不知道,哈哈)

    通过使用malloc(),程序可以在运行时决定需要多大的数组并创建它。

    程序清单12.14 举例证明了这可能。

    它把内存块地址赋给指针ptd,接着以使用数组名的方式使用ptd。

    程序还调用了exit()函数。该函数的原型在 stdlib.h 中,用来在内存分配失败时结束程序

    • EXIT_FAILURE 也在这个头文件中定义。标准库提供了两个保证能够在所有操作系统下工作的返回值:
      EXIT SUCCESS(或者,等同于0)指示程序正常终止;
      EXIT_FAILURE指示程序异常终止。

      另外,有些操作系统,包括UNIX、Linux 和Windows,能够接受其他的整数值。

    程序清单12.14 dyn_arr.c 程序

    /*dyn_arr.c -- 为数组动态分配存储空间*/
    #include<stdio.h>
    #include<stdlib.h>
    
    int main(void) {
    	double* ptd;
    	int max;
    	int number;
    	int i = 0;
    
    	puts ("What is the maximum number of type double entries?" );
    	//entries:entry 输入
    	scanf("%d", &max);//输入个数
    	ptd = (double*)malloc(max * sizeof(double));
    	if (ptd == NULL) {
    		exit(EXIT_FAILURE);
    	}
    	///*ptd现在指向有max个元素的数组*/
    	puts("Enter the values(q to quit):");
    	while (i < max && scanf("%lf", &ptd[i]) == 1)
    		++i;
    		
    	printf("Here are your %d entries:\n", number = i);
    	for(i = 0; i < number; i++){
    		printf("%7.2f", ptd[i]);
    		/*.2表示输出数据保留小数点后2位小数,第3位四舍五入;
    		7表示输出数据在终端设备上占用7个字符宽度右对齐,
    		实际数据位数(包括小数点)小于7时左边用空格补齐,
    		大于7时按实际位数向右扩展输出。*/
    		if(i % 7  == 6)
    			putchar('\n');
    	}
    	if(i % 7 != 0)
    		putchar('\n');
    	puts("Done.");
    	free(ptd);
    	return 0; 
    }
    

    下面是一个运行示例。该例中输入了6个数,但程序只处理了5个,因为我们将数组大小限定为5。
    在这里插入图片描述
    代码分析:

    1. 程序通过下列几行获取所需的数组大小:
    puts ( "What is the maximum number of type double entries?" );
    scanf("%d", &max);//输入个数
    
    1. 接着,下面的行分配对于存放所请求数目的项来说足够大的内存,并将该内存块的地址赋给指针ptd:
    ptd = (double*)malloc(max * sizeof(double));
    
    1. 在C中,类型指派(double*)是可选的,而在C++中必须有,因此使用类型指派将使把C程序移植到C++更容易。

      malloc()可能无法获得所需数量的内存。在那种情形下,函数返回空指针,程序终止。
    	if (ptd == NULL) {
    		exit(EXIT_FAILURE);//内存分配失败
    	}
    

    如果成功地分配了内存,程序将把ptd视为一个具有max个元素的数组的名字。

    • 注意
      在程序末尾附近的函数free()。
      它释放malloc()分配的内存。
      函数free()只释放它的参数所指向的内存块。
      在这个特定例子中,使用free()不是必须的,因为在程序终止后所有已分配的内存都将被自动释放。
      然而在一个更复杂的程序中,能够释放并再利用内存将是重要的。

    • 使用动态数组将获得什么?

      主要是获得了程序灵活性
      假定知道一个程序在大多数情况下需要的数组元素不超过100个;
      而在某些情况下,却需要l0000个元素。
      在声明数组时,不得不考虑到最坏情形并声明一个具有10000个元素的数组。
      在多数情况下,程序将浪费内存。
      如果有一次需要10001个元素,程序就会出错。
      您可以使用动态数组来使程序适应不同的情形。


    free ( )

    在编译程序时,静态变量的数量是固定的:在程序运行时也不改变。

    自动变量使用的内存数量在程序执行时自动增加或者减少。
    但被分配的内存所使用内存数量只会增加,除非您记得使用free()。

    例如,假定有一个如下代码勾勒出的函数,它创建一个数组的临时拷贝:

    #include<stdio.h>
    #include<malloc.h>
    
    void gobble (double ar[], int n);
    
    int main(){
    	double glad[2000];
    	int i;
    	
    	for(i = 0; i<100000000; i++){
    		gobble(glad, 2000);
    	}
    }
    
    void gobble(double ar[], int n){
    	double *temp = (double *) malloc(n*sizeof(double)) ;
    	
    //	free(temp);/*忘记使用*/
    }
    
    • 第一次调用gobble()时,它创建了指针temp,并使用malloc()为之分配16000字节的内存(设double是8个字节)。
      假定我们如暗示的那样没有使用free()。
      当函数终止时,指针temp作为一个自动变量消失了。
      但它所指向的16000个字节的内存仍旧存在。
      我们无法访问这些内存,因为地址不见了。
      由于没有调用free(),不可以再使用它了。
    • 第二次调用gobble(),它又创建了一个temp,再次使用malloc()分配16000个字节的内存。
      第一个16000字节的块已不可用,因此malloc()不得不再找一个l6000字节的块。
      当函数终止时,这个内存块也无法访问,不可再利用。

    但循环执行了1000次,因此在循环最终结束时,已经有1600万字节的内存从内存池中移走。
    事实上,在到达这一步前,程序很可能已经内存溢出了。

    这类问题被称为内存泄漏(memory leak),可以通过在函数末尾处调用free()防止该问题出现。


    calloc( )

    内存分配还可以使用calloc()。

    典型的应用如下:

    long * newmen;
    newmen = (long *) calloc (100, sizeof( long)); 
    

    与 malloc()类似,calloc()在ANSI以前的版本中返回一个 char 指针,在ANSI中返回一个void指针。
    如果要存储不同类型,应该使用类型指派运算符
    这个新函数接受两个参数,都应是无符号的整数(在ANSI中是size_t类型)。

    第一个参数是所需内存单元的数量,第二个参数是每个单元以字节计的大小

    在这里,long使用4个字节,因此这一指令建立了100个4字节单元,总共使用400个字节来存储。

    使用 sizeof(long)而不是 4 使代码更具可易移植性。
    它可在其他系统中运行,这些系统中 long不是4字节而是别的大小。

    函数calloc()还有一个特性:
    它将块中的全部位都置为0(然而要注意,在某些硬件系统中,浮点值0不是用全部位为0来表示的)。

    • ps:个人觉得它相当于数组的初始化。
      在这里插入图片描述

    函数free()也可以用来释放由calloc()分配的内存。

    动态内存分配是很多高级编程技巧的关键。

    在17章“高级数据表示”中我们将研究一些。您自己的C库可能提供了其他内存管理函数,有些可移植,有些不可以。您可能应该抽时间看一下。


    动态内存分配与变长数组

    变长数组(Variable-Length Array,VLA)malloc()在功能上有些一致。例如,它们都可以用来创建一个大小在运行时决定的数组:

    int valmal(){
    	int n;
    	int * pi;
    	scanf("%d", &n);
    	
    	pi = (int *) malloc(n*sizeof(int));
    	int ar[n];//变长数组 
    	
    	pi[2] = ar[2] = -5;
    }
    

    一个区别在于 VLA 是自动存储的。

    自动存储的结果之一就是 VLA 所用内存空间在运行完定义部分之后会自动释放

    在本例中,就是函数 vlamal()终止的时候。因此不必使用 free()。

    另一方面,使用由 malloc()创建的数组不必局限在一个函数中。

    例如,函数可以创建一个数组并返回指针,供调用该函数的函数访问。
    接着,后者可以在它结束时调用 free()。
    free()可以使用不同于 malloc()指针的指针变量:必须一致的是指针中存储的地址

    VLA 对多维数组来说更方便
    您可以使用 malloc()来定义一个二维数组,但语法很麻烦。
    如果编译器不支持 VLA 特性,必须固定一维的大小,正如下面的函数调用:

    	int n = 5;
    	int m = 6;
    	int ar2[n][m];//n*m 的变长数组 
    	int(* p2) [6];//在c99之前可以使用 
    	int(* p3) [m];//要求变长数组支持 
    	
    	p2 = (int (*)6) malloc (n * 6 * sizeof(int));// n*6 数组 
    	p3 = (int (*)[m]) malloc (n * m * sizeof(int));// n*m 数组 
    	//上面的表达式也要求变长数组支持 
    	
    	ar2[1][2] = p2[1][2] = 12;
    

    有必要查看一下指针声明。函数malloc()返回一个指针,因此p2必须是适当类型的指针。
    下面的声明:

    int(* p2) [6];//在c99之前可以使用 
    

    表明p2指向一个包含6个int值的数组
    这意味着p2[j| 将被解释为一个由6个整数构成的元素,p2[ i ][ j ]将是一个int值。

    第二个指针声明使用变量来指定p3所指数组的大小。
    这意味着p3将被看作一个指向 VLA 的指针,这正是代码不能在C90标准中运行的原因。


    内存类与动态内存分配

    您可能正在为存储类和动态内存分配之间的联系感到疑惑。

    • 我们来看一个理想模型。
      可以认为程序将它的可用内存分成了三个独立的部分:
    • 一个是具有外部链接的、具有内部链接的以及具有空链接的静态变量的:
    • 一个是自动变量的:
    • 另一个是动态分配的内存的。

    (静态变量):
    在编译时就已经知道了静态存储时期存储类变量所需的内存数量,存储在这一部分的数据在整个程序运行期间都可用

    这一类型的每个变量在程序开始时就已存在,到程序结束时终止。

    (动态变量):
    然而,一个自动变量在程序进入包含该变量定义的代码块时产生,在退出这一代码块时终止。

    因此,伴随着程序对函数的调用和终止,自动变量使用的内存数量也在增加和减少。

    典型地,将这一部分内存处理为一个堆栈

    这意味着在内存中,新变量在创建时按顺序加入,在消亡时按相反顺序移除

    (动态内存分配):
    动态分配的内存在调用malloc()或相关函数时产生,在调用free()时释放。

    由程序员而不是一系列固定的规则控制内存持续时间,因此内存块可在一个函数中创建,而在另一个函数中释放。(malloc 可以跨函数调用)

    由于这点,动态内存分配所用的内存部分可能变成碎片状,也就是说,在活动的内存块之间散布着未使用的字节片

    不管怎样,使用动态内存往往导致进程比使用堆栈内存慢


    本文选自《C primer plus》

    展开全文
  • C语言动态内存分配函数

    万次阅读 多人点赞 2019-06-02 23:46:57
    目录 1.malloc()2.free()3.calloc()4.realloc()5....所开辟的内存是在栈中开辟的固定大小的 ,如a是4字节 ,数组b是40字节 ,并且数组在申明时必须指定其长度 , 因为数组的内存是在编译时分配好的 . 如果我们想在...

    目录

    1.malloc()
    2.free()
    3.calloc()
    4.realloc()
    5.小结


     在C中我们开辟内存空间有两种方式 :
    1.静态开辟内存 : 例如:

    int a;
    int b[10];

     这种开辟内存空间的特点是
    所开辟的内存是在栈中开辟固定大小的 ,如a是4字节 ,数组b是40字节 ,并且数组在申明时必须指定其长度 , 如果是全局数组的话,内存是在编译时分配好的,如果是局部变量数组的话,运行时在栈上静态分配内存。不管是全局数组还是局部数组,它们都有一个特点,那就是数组大小是确定的,是代码中写死的。那如果我们想在程序运行时才确定一个数组的大小 , 前两种在栈上分配内存的方法显然是不行的 , 举个例子 :

    int n;
    scanf("%d", &n);
    int a[n];

    这样编写会在编译时出错 , 编译器会提醒[ ]中应为常量表达式 , 在C中定义数组时可以用的有以下几种 ,例:

    #define N 10
    enum NUM{
    	M=10
    };
    int a1[N];
    int a2[10];
    int a3[M];

    需要注意的是 ,C中const int n =10 ; n并不能作为数组长度定义数组 , 但C++中则可以 , 
    但我们对于开辟空间的需求 , 往往不限于此 , 最常见的定义数组时数组大小在程序运行时才知道的 , 静态开辟就已经无能为力 . 当然有静态开辟 ,肯定也有动态开辟 ,接下来我们就来看动态开辟内存空间

    2.动态开辟内存 :
    在C中动态开辟空间需要用到三个函数 :
    malloc(), calloc(), realloc() ,这三个函数都是向堆中申请的内存空间.
    在堆中申请的内存空间不会像在栈中存储的局部变量一样 ,函数调用完会自动释放内存 , 需要我们手动释放 ,就需要free()函数来完成.
    下面让我们来看看这几个函数各自的特点, 用法, 区别, 联系.

    1.malloc()

    void * malloc(size_t size)

    1).malloc()函数会向中申请一片连续可用内存空间
    2).若申请成功 ,,返回指向这片内存空间的指针 ,若失败 ,则会返回NULL, 所以我们在用malloc()函数开辟动态内存之后, 一定要判断函数返回值是否为NULL.
    3).返回值的类型为void*型, malloc()函数并不知道连续开辟的size个字节是存储什么类型数据的 ,所以需要我们自行决定 ,方法是在malloc()前加强制转 ,转化成我们所需类型 ,如: (int*)malloc(sizeof(int)*n).
    4).如果size为0, 此行为是未定义的, 会发生未知错误, 取决于编译器

    具体怎么用呢 ,举个例子 .

    int *p = NULL;
    int n = 0;
    scanf("%d", &n);
    p = (int*)malloc(sizeof(int) * n);
    if(p != NULL){
        //....需要进行的操作
    }
    

    这时就相当于创建了一个数组 p[n] ,这个n的值并不需要像定义一个普通数组一样必须是常量, 可以使程序运行时得出的, 或是用户输入的


     2.free()

    void free(void* ptr)
    在堆中申请的内存空间不会像在栈中存储的局部变量一样 ,函数调用完会自动释放内存 , 如果我们不手动释放, 直到程序运行结束才会释放, 这样就可能会造成内存泄漏, 即堆中这片内存中的数据已经不再使用, 但它一直占着这片空间, (通俗说就是就是占着茅坑不拉屎), 所以当我们申请的动态内存不再使用时 ,一定要及时释放 .

    1).如果ptr没有指向使用动态内存分配函数分配的内存空间,则会导致未定义的行为。
    2).如果ptr是空指针,则该函数不执行任何操作。
    3).此函数不会更改ptr本身的值,因此它仍指向相同(现在已经无效)的位置(内存)
    4).free()函数之后需要将ptr再置空 ,即ptrNULL;如果不将ptr置空的话 ,后面程序如果再通过ptr会访问到已经释放过无效的或者已经被回收再利用的内存, 为保证程序的健壮性, 一般我们都要写ptrNULL;

    注意 : free()不能重复释放一块内存, 如:

    free(ptr);
    free(ptr);

     是错的, 已经释放过的内存不能重复释放, 会出现内存错误 .

    free()具体用法, 举个例子 :

    int *p = NULL;
    int n = 0;
    scanf("%d", &n);
    p = (int*)malloc(sizeof(int) * n);
    if(p != NULL){
        //....需要进行的操作
    }
    //操作完成 ,不再使用这片内存空间
    free(p);
    p = NULL;

     


     

     3.calloc()

    void * calloc(size_t num,size_t size)
    malloc()函数的区别只在于, calloc()函数会在返回地址之前将所申请的内存空间中的每个字节都初始化为0 .

    1).calloc()函数功能是动态分配num个大小(字节长度)为size的内存空间 .
    2).若申请成功 ,,返回指向这片内存空间的指针 ,若失败 ,则会返回NULL, 所以我们在用calloc()函数开辟动态内存之后, 一定要判断函数返回值是否为NULL.
    3).返回值的类型为void*型, calloc()函数虽然分配numsize大小的内存空间 ,但还是不知道存储的什么类型数据 ,所以需要我们自行决定 ,方法是在calloc()前加强制转 ,转化成我们所需类型 ,如: (int*)calloc(num, sizeof(int)).
    4).如果sizenum有一个或都为0, 此行为是未定义的, 会发生未知错误, 取决于编译器

    所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成这个需求。
    例如 :


    4.realloc()

    void * realloc(void * ptr,size_t size)

    realloc()函数让动态内存管理更加灵活 .在程序运行过程中动态分配内存大小,  如果分配的太大 ,则浪费空间, 如果太小, 可能还是会出现不够用的情况 .为了合理的利用内存,我们一定会对内存的大小做灵活的调整。那realloc() 函数就可以做到对动态开辟内存大小的调整(既可以往大调整, 也可以往小了调整) .

    1).ptr为需要调整的内存地址
    2).size为调整后需要的大小(字节数)
    3).若调整成功, 返回值为调整大小后内存的起始位置(也就是指向调整后内存的指针), 若失败(当没有内存可以分配时, 一般不会出现), 则返回NULL, 所以还是要对返回值判空
    4).如果ptr是空指针, 则和malloc()函数一样作用一样

    注意 : realloc()函数在扩大内存空间时有两种情况
    1).ptr所指的内存后有足够的内存空间用来扩展 ,如图 :

    2).ptr所指内存后没有足够的空间来扩展 ,如图 :
     

    当第二种情况时, 若申请新的内存空间成功, 会将ptr所指向的内存中的内容拷贝到新的内存空间中, ptr所指向的内存会被释放, 返回新得内存地址, 若不成功 ,ptr 所指内存不会被释放, 函数返回NULL


    5.小结

    1).malloc()calloc()函数用法一样, 唯一的区别是calloc()会对所申请内存的每个字节初始化为0

    2).malloc()calloc(), realloc()申请的内存不再使用时 ,一定要用free()释放 ,否则会造成内存泄漏

    3).p = realloc(ptr, size)函数返回值不为空时, 释放内存时不需写free(ptr) ,只需写free(p) 

     

     

    展开全文
  • C/C++ 动态内存分配

    万次阅读 多人点赞 2019-06-30 18:03:06
    首先我们看看 c 语言是如何进行动态内存分配的; c 语言主要是使用malloc / calloc / realloc 来进行内存申请的。 malloc / realloc / calloc三者的共同点与不同点: 共同点 1、都是从堆上进行动态内存分配 2、...

    转自:https://blog.csdn.net/Code_beeps/article/details/89625473#comments

    首先我们看看 c 语言是如何进行动态内存分配的;
    c 语言主要是使用malloc / calloc / realloc 来进行内存申请的。
    malloc / realloc / calloc三者的共同点与不同点:

    共同点

    都是从堆上进行动态内存分配
    释放内存都是需要使用free函数来释放
    三者的返回值都是void*
    都需要强制类型转换
    都需要对申请出的空间判空(因为申请内存失败会返回空)
    

    不同点:
    1)void *malloc( size_t size );
    malloc的参数是用户所需内存空间大小的字节数,不会对申请成功的内存初始化。
    malloc 申请空间时并不是需要多少就申请多少,而是会多申请一些空间,1)多申请一个32字节的结构体,里面对申请的空间进行描述,2)在申请的空间前后会各多申请 4 个字节的空间,这就是保护机制,当你操作不当越界了,这 8 个字节的内容会改变,操作系统会检查前后 4 个字节是否改变了,以此判断是否越界了。

    在这里插入图片描述
    在这里插入图片描述
    2)void *calloc( size_t num, size_t size );
    calloc的参数:第一个:元素的个数,第二个:单个元素所占字节;会把申请成功的空间初始化为 0

    3)void *realloc( void *ptr, size_t size );
    realloc的参数:第一个:地址,第二个:字节数
      对于 realloc 的第一个参数:
        如果指向空,那么此时realloc 就和 malloc 是一样的;
        如果不为空,那么就将即将要申请的空间与原空间进行比较。
            如果要申请的空间比原空间小,就将原空间缩小,并返回原空间首地址
            如果要申请的空间比原空间大,那么分两种情况:
            第一种:新旧空间之差小于原空间大小,直接在原空间进行延伸,并返回原空间的首地址。
            第二种:新旧空间之差大于原空间的大小,那么直接开辟新空间,并将原空间的数据拷贝到新空间,并返回新空间首地址。
    在这里插入图片描述
    看完了 C 动态内存管理,那么来看看C++的动态内存管理:
    首先 C 语言中的动态内存管理方式在 C++ 中仍然可以使用。c++ 中多了 new 这个操作符。
    new申请的空间:

    1、不用强制类型转换;
    2、不用对申请出来的空间进行判空;
    3、可以申请时初始化这段空间。
    

    malloc / new / new[] 和 free / delete / delete[]
    对于内置类型:如果没有配合使用,可能不会出现什么问题。
    对于自定义类型:
      malloc:只是将空间动态开辟成功,并不会调用构造函数初始化空间。
      free:只是将申请的空间进行释放,并不会调用析构函数清理对象中的资源

    new:先将对象的空间开辟成功,然后调用构造函数完成对象的初始化。
      delete:先调用析构函数将对象中的资源清理,然后释放对象占用的空间
    如果对一个内部有资源的自定义类型使用 malloc 开辟内存,此时调用 delete 进行空间的释放,程序就会崩溃。因为 malloc 只负责开辟空间,并不会调用对象的构造函数对其成员变量进行初始化,那么内部的成员变量是没有分配空间的,当我们调用 delete 时,delete会先对对象进行资源清理,但是对象里的资源 malloc 并没有给其分配,所以我们清理的时候是非法的操作。导致程序崩溃。

    对于内部有资源的自定义类型,使用 new 开辟的空间使用 free 释放,会造成内存泄漏,因为 free 并不会调用析构函数清理对象的资源,因此会造成资源泄漏。

    new底层的流程:

    第一步:调用operator new() 来申请空间
    第二步:调用该类的构造函数
    

    operator new() 的工作流程:
    比如 new T:底层调用 void* operator new(sizeof(T)),申请 T 类型大小的堆空间。
      在这个函数中是循环调用 malloc :

      1、申请空间成功:返回空间的首地址。
      2、申请空间失败:检测用户是否提供空间不足的应对措施?如果提供应对措施,则执行应对措施,否则直接抛出 bad_alloc 类型的异常。
    

    在这里插入图片描述
    new[]的流程:
    第一步:调用void* operator new[](count = sizeof(T) * N + 4),如果T类的析构函数显式提供就多申请4个字节。(多申请的四个字节就是用来保存对象的个数,可以知道未来需要调用几次析构函数。)
      这里operator new 的运行流程和上图一致。
    第二步:将空间前四个字节填充对象的个数,然后调用构造函数构造 N 个 T 类型对象。

    delete的流程:
    第一步:调用析构函数清理对象中的资源。
    第二步:调用void operator delete(void* p)释放空间,void operator delete(void* p)中调用的是 free 释放空间。

    delete[] 的流程:
    第一步:从第一个对象空间之前的4个字节中取对象的个数N
    第二步:调用N次析构函数倒着释放(栈的特性)
    第三步:void operator delete[](void* p)----这个p就是真正使用位置倒退4个字节的位置,也就是申请的空间的首地址。
    在这里operator delete[](void* p) 调用 void operator delete(void* p) 调用 free()

    综上:operator new 和 operator delete 实际上是由 malloc 和 free 来实现的,是对malloc 和 free 的封装。

    malloc/free 和 new/delete 区别:
    共同点:都在堆上申请空间,都需要手动申请 / 释放空间。
    不同点:

      1)malloc/free 是函数,new/delete是标识符
      2)malloc 不会对对象进行初始化,new 可以初始化
      3)malloc 申请空间时,需要手动计算需要申请空间的大小,而new只需在后面跟上类型,编译器会自动计算大小。
      4)malloc 返回值是 void*,使用时必须要强制类型转换,而 new 并不需要强制类型转换,因为new后跟的就是类型。
      5)malloc 申请空间失败返回 NULL,因此使用时必须判空,new不需要判空,但是需要捕获异常
      6)申请自定义类型对象时,malloc/free只会开辟空间,并不调用构造/析构函数,而 new 是先申请空间,
      然后调用构造函数完成对象的初始化,delete 在释放空间前会先清理对象占用的资源。
      7)malloc/free 的效率会比 new/delete 的高,因为 new/delete 中封装的是malloc/free。
    
    展开全文
  • 动态内存管理详解

    千次阅读 2018-11-29 14:56:56
    C语言中开辟内存有很多种方式,目前我们最常用的也就是数组,但数组是在我们用到他之前就得设定好它的长度,... 所以,为了填补这一缺口,c语言有了动态内存,c语言提供了几个函数来管理我们的动态内存,这几个函数...
  • C语言中指针动态内存的申请和释放

    千次阅读 2020-08-20 21:10:28
    什么是动态内存的申请和释放? 当程序运行到需要一个动态变量时,需要向系统的堆中申请一块内存,用来存储这块变量,这就是内存的申请。当程序不需要这个变量时,就需要把申请的这块内存释放掉,这就是内存的释放...
  • c语言动态内存分配

    千次阅读 2019-01-03 23:00:21
    动态内存分配是许多高级程序设计技巧的关键。 所有程序都必须留下足够的内存来存储程序使用的数据。这些内存中,有些是自动分配的(栈),有些是在编译时就确定好的(静态区),c语言还可以实现动态的/运行时的内存...
  • 动态内存分配函数

    千次阅读 2018-03-28 21:43:28
    一、静态存储分配与动态存储分配: 二、动态内存分配函数 malloc calloc realloc free new delete
  • 数组大小分配(动态内存分配)

    千次阅读 2020-02-22 21:31:40
    在使用数组的时候,总是有一个问题,数组应该有多大?...这种分配固定大小内存分配的方法称为静态内存分配。但是这种分配方法存在比较严重的缺陷,特别是处理某些问题时,在大多数情况下会浪费大量的...
  • 动态内存分配(C语言)

    千次阅读 多人点赞 2021-05-13 20:43:50
    动态内存分配 不放过每一个值得关注的点 文章目录动态内存分配前言一、pandas是什么?二、使用步骤1.引入库2.读入数据总结 前言 我们创建一个一维的数组的时候,可以是静态分配的,也可以是动态分配的。 静态分配...
  • 首先我们先了解一下内存:C语言使用malloc/free动态管理内存空间,C++引入了new/delete,new[]/delete[]来动态管理内存。 介绍new/delete,new[]/delete[]之前我们先了解一下operator new,operator delete,...
  • LwIP 之五 详解动态内存管理 内存堆(mem.c/h)

    千次阅读 多人点赞 2018-05-13 11:24:56
    LwIP为了能够灵活的使用内存,为使用者提供两种简单却又高效的动态内存管理机制:动态内存堆管理(heap)、动态内存池管理(pool)。这两中内存管理策略的实现分别对应着源码文件mem.c/h和memp.c/h。   其中,...
  • 浅谈动态内存分配的意义

    千次阅读 2020-12-17 14:54:25
    在复习数组的时候,我看到可以用一个变量N来定义数组长度,即a[N],我有点纳闷,既然数组长度可以是变量,为什么会有动态内存分布这种用法去malloc一个内存而创造数组,参考以下两段代码,两个程序运行起来的效果看...
  • 数组的动态内存分配

    千次阅读 2019-03-12 17:13:00
    1.已知数组的第二维进行动态内存分配 方式:利用数组指针 #include &amp;amp;lt;iostream&amp;amp;gt; using namespace std; int main(int argc,char *argv[]) { const int N=5; //列数 int m=4; //行数 ...
  • C++中类的动态内存分配与释放

    千次阅读 2019-09-21 22:01:09
      一般来讲,编译器使用三块独立的内存:   1....而堆就比较厉害了,堆负责动态内存分配。什么是动态内存呢?大部分的变量,在编译后就已经确定了自己在内存的位置;而有些变量则是在程序运行过...
  • 文章目录1 动态内存分配的意义1.1 C语言中如何动态申请内存空间1.2 malloc和free的用法1.3 calloc与realloc1.31 calloc和realloc的代码案例分析2 总结 1 动态内存分配的意义 在C语言中,一切操作都是基于内存的。...
  • 使用动态内存可能出现的问题总结(笔试题解析)

    千次阅读 多人点赞 2018-08-02 15:56:19
      在使用动态内存分配程序中,常常会产生不易发现的错误,这其中就包括对NULL指针进行解引用,访问未知内存区域(越界访问&amp;amp;amp;访问未初始化指针指向区域),内存泄漏,释放非动态内存开辟空间(向...
  • 摘要:鸿蒙轻内核M核新增支持了多段非连续性内存区域,把多个非连续性内存逻辑上合一,用户不感知底层的不同内存块。
  • C++二维数组动态内存分配【转】

    千次阅读 2018-10-17 09:43:32
    (转自:... /*申请3行4列的二维数组内存*/ int **array; array = new int *[3];/*先申请3个int*类型的一维指针,作为行*/ for (i = 0; i < 3; i++) {/*再为每一行申请一个...
  • 动态内存与静态内存的区别

    千次阅读 2019-01-08 06:04:08
    1. 静态内存 静态内存是指在程序开始运行时由编译器分配的内存,它的分配是在程序开始编译时完成的,不占用CPU资源。 ...2. 动态内存 用户无法确定空间大小,或者空间太大,栈上无法分配时,...
  • 动态内存的申请和释放

    千次阅读 2019-01-20 22:15:18
    动态内存的申请和释放 文章目录动态内存的申请和释放1. malloc() 和 free() 的基本概念以及基本用法1.1 函数原型及说明:1.2 被释放的指针1.3 注意事项2. malloc() 到底从哪里得来了内存空间2.1 堆介绍2.2 栈介绍2.3...
  • 浅析C++中的动态内存分配

    千次阅读 2018-04-09 15:28:58
    在C语言中,我们学到了使用malloc/calloc/relloc来进行内存动态开辟,用free来完成内存的释放。而在C++中是通过new和delete这两个操作符来实现的。当然C++中也可以使用C语言那套内存管理的方法,毕竟C++是兼容...
  • 静态内存分配与动态内存分配

    千次阅读 2019-05-25 14:05:02
    静态内存分配与动态内存分配 动机 平时看c/c++的书籍时,总会看到一种观点,说是C/C++语言使用的时候动态内存分配是最重要的,使用malloc等函数分配的内存必须要释放,否则及其容易出现内存泄露。但是自己有时候挺...
  • C语言中手把手教你动态内存分配

    万次阅读 多人点赞 2017-02-06 23:21:07
    C语言中手把手教你动态内存分配动态内存分配常见的内存分配的错误先上一个内存分配的思维导图:便于联想想象,理解: 首先我们介绍一下内存分配的方式:1:在静态存储区域中进行分配 内存在程序编译的时候就已经分配...
  • 使用动态内存分配生成动态数组来完成矩阵转置,使用指针实现函数的功能 AC的C++代码如下: #include<iostream> using namespace std; void transpose(int **a,int **b,int row,int col) { for(int i=0;i&...
  • 动态内存申请(malloc, calloc, new)之分配虚拟内存空间和物理内存空间 1. 动态内存申请的底层系统调用 动态内存申请函数根据申请的内存大小选择不同的系统调用,小于128K选择brk系统调用分配内存,大于128K选择...
  • 一个由C/C++编译的程序占用的内存分为以下几个部分: 1、栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。所以在局部变量中尽量不要使用大数组,容易...
  • 动态内存和静态内存浅析

    千次阅读 2018-01-13 16:05:37
    由于堆空间只能在程序运行时被使用,因此堆空间也被称动态内存。另外,动态内存只能在程序运行时通过指正 对分配给各种变量、字符串和数组(只能通过指针来访问堆空间)。动态分配内存需要使用函数:malloc(),...
  • c++:为什么要使用动态内存分配

    千次阅读 多人点赞 2018-08-29 14:59:45
    // 方式二:动态内存分配法 A * a = new A(); 两者有什么差别呢? 实际上,方式二即等价于如下代码: A * a = new A(); 等价于 A * a; a = new A(); 方式一就是直接将a放入栈区(局部变量,大小受限,自动...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,069,840
精华内容 427,936
关键字:

动态内存