精华内容
下载资源
问答
  • 内存分配后必须判断是否成功
    千次阅读
    2012-03-14 22:08:48

    内存分配的过程中容易出现以下的错误:

       1,内存未分配成功,却在程序中使用了,解决办法是:使用前先进行判断

       2,内存分配成功后,未进行初始化,解决办法是:初始化为常量值

       3,内存分配成功后,初始化后,对内存的使用超过了边界。解决办法:注意边界条件

       4,使用分配的内存后忘记释放内存。解决办法:释放与申请相对应。

       5,释放后继续使用内存,解决办法:释放后将指向内存的指针赋值为NULL

    c语言中的内存分配:

           C语言中采用malloc()和free()函数进行内存的分配和释放,当然还有其他内存分配的函数(calloc()和realloc())。此文只对malloc()和free()函数进行了讨论。

         calloc()函数:void* calloc(size_t num, size_t size_of_element)

                           说明:    其中num为元素的个数,size_of_element为每个元素所占的空间大小,申请的空间大小为:num*size_of_element,  同时每个空间被初始化为零。

    #include<stdio.h>
    #include<stdlib.h>
    int main(void)
    {
    	int i;
    	int *pn=(int *)calloc(10,sizeof(int));
    	for(i=0;i<10;i++)
    		printf("%3d",*pn++);
    	printf("\n");
    	free(pn);
    	return 0;
    } 

          realloc()函数: void*  realloc(void* mem_address, size_t newsize)

    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	int i;
    	int *pn=(int *)malloc(5*sizeof(int));
    	printf("%p\n",pn);
    	for(i=0;i<5;i++)
    		scanf("%d",&pn[i]);
    	pn=(int *)realloc(pn,10*sizeof(int));
    	printf("%p\n",pn);
    	for(i=0;i<5;i++)
    		printf("%3d",pn[i]);
    	printf("\n");
    	free(pn);
    	return 0;
    } 
                          说明:  按照newsize的大小申请一个空间,将原来数据从地址mem_address中拷贝到新的地方,释放原来的内存,同时返回指向新的内存的指针。 realloc失败的时候,返回NULL;   realloc失败的时候,原来的内存不改变,不会释放也不会移动;传递给realloc的指针必须是先前通过malloc(), calloc(), 或realloc()分配的;   假如原来的内存后面还有足够多剩余内存的话,realloc的内存=原来的内存+剩余内存,realloc还是返回原来内存的地址; 假如原来的内存后面没有足够多剩余内存的话,realloc将申请新的内存,然后把原来的内存数据拷贝到新内存里,原来的内存将被free掉,realloc返回新内存的地址;   如果size为0,效果等同于free()。这里需要注意的是只对指针本身进行释放,例如对二维指针**a,对a调用realloc时只会释放一维,使用时谨防内存泄露。

    一、malloc()和free()的基本概念以及基本用法:

    1、函数原型及说明:
             void *   malloc(long NumBytes):该函数分配了NumBytes个字节,并返回了指向这块内存的指针。如果分配失败,则返回一个空指针(NULL)。在写代码的过程一定要对内存分配失败与否进行判断。

             void      free(void *FirstByte):该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。

            char *pChar = NULL;
    	pChar = (char *)malloc(10*sizeof(char));
    	if (NULL == pChar)   //check the malloc()'s return
    	{
    		cout<<"memory failed!"<<endl;
    		exit(1);
    	}
    	//do something 
    	free(pChar);
            pChar = NULL;

    
    

    2     关于函数使用需要注意的一些地方:       A、申请了内存空间后,必须检查是否分配成功。       B、当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。       C、这两个函数应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。        D、虽然malloc()函数的类型是(void *),任何类型的指针都可以转换成(void*),但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查。3    深层次理解malloc()和free()

    3.1、malloc()到底从哪里得到了内存空间?从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找合适的空间(不同的内存管理办法采用不同的分配原则),然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。就是这样!(具体的怎么去分配的可以看有关OS的书中内存管理的相关章节,内存管理有:分页式管理,分段式管理和两种方式结合)     

     3.2、什么是堆:堆是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。什么是栈:栈是线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立。每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

    栈是由编译器自动分配释放,存放函数的参数值、局部变量的值等。操作方式类似于数据结构中的栈。堆一般由程序员分配释放,若不释放,程序结束时可能由OS回收。注意这里说是可能,并非一定。所以我想再强调一次,记得要释放!注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。所以,举个例子,如果你在函数上面定义了一个指针变量,然后在这个函数里申请了一块内存让指针指向它。实际上,这个指针的地址是在栈上,但是它所指向的内容却是在堆上面的!这一点要注意!所以,再想想,在一个函数里申请了空间后,比如说下面这个函数:程序代码:

    void Function(void)
    {
            char *p = (char *)malloc(100 * sizeof(char));
    }




    就这个例子,千万不要认为函数返回,函数所在的栈被销毁指针也跟着销毁,申请的内存也就一样跟着销毁了!这绝对是错误的!因为申请的内存在堆上,而函数所在的栈被销毁跟堆完全没有啥关系。所以,还是那句话:记得释放
             3.3、free()到底释放了什么

             free()释放的是指针指向的内存!注意!释放的是内存,不是指针!这点非常非常重要!指针是一个变量,只有程序结束时才被销毁。释放了内存空间后,原来指向这块空间的指针还是存在!只不过现在指针指向的内容的垃圾,是未定义的,所以说是垃圾。因此,前面我已经说过了,释放内存后把指针指向NULL,防止指针在后面不小心又被解引用了。非常重要啊这一点!

      
    	free(pChar);
            pChar = NULL;    //赋值为NULL

    4   malloc()以及free()的机制:


            仔细看一下free()的函数原型,也许也会发现似乎很神奇,free()函数非常简单,只有一个参数,只要把指向申请空间的指针传递
    给free()中的参数就可以完成释放工作!这里要追踪到malloc()的申请问题了。申请的时候实际上占用的内存要比申请的大。因为超出的空间是用来记录对这块内存的管理信息。先看一下在《UNIX环境高级编程》中第七章的一段话:

    大多数实现所分配的存储空间比所要求的要稍大一些,额外的空间用来记录管理信息——分配块的长度,指向下一个分配块的指针等等。这就意味着如果写过一个已分配区的尾端,则会改写后一块的管理信息。这种类型的错误是灾难性的,但是因为这种错误不会很快就暴露出来,所以也就很难发现。将指向分配块的指针向后移动也可能会改写本块的管理信息。

    以上这段话已经给了我们一些信息了。malloc()申请的空间实际我觉得就是分了两个不同性质的空间。一个就是用来记录管理信息的空间,另外一个就是可用空间了。而用来记录管理信息的实际上是一个结构体。在C语言中,用结构体来记录同一个对象的不同信息是
    下面看看这个结构体的原型:
    程序代码:

    struct mem_control_block {
               int is_available;  
               int size;//这是实际空间的大小
         };





    对于size,这个是实际空间大小。

    所以,free()就是根据这个结构体的信息来释放malloc()申请的空间!而结构体的两个成员的大小我想应该是操作系统的事了。但是这里有一个问题,malloc()申请空间后返回一个指针应该是指向第二种空间,也就是可用空间!不然,如果指向管理信息空间的话,写入的内容和结构体的类型有可能不一致,或者会把管理信息屏蔽掉,那就没法释放内存空间了,所以会发生错误!
    好了!下面看看free()的源代码,我自己分析了一下,觉得比起malloc()的源代码倒是容易简单很多。
    程序代码:
    void free(void *ptr)
    {
    struct mem_control_block *free;
    free = ptr - sizeof(struct mem_control_block);
    free->is_available = 1;
    return;
    }

    c++语言中的new和delete运算符

    注意此处的new和delete是运算符而不是函数。c++中如果使用malloc()和free(),不能调用构造函数和析构函数,这破坏了空间分配、初始化的功能。所以引入了new和delete。

    new的用法



    1.     开辟单变量地址空间

             1)new int;  //开辟一个存放数组的存储空间,返回一个指向该存储空间的地址.int *a = new int 即为将一个int类型的地址赋值给整型指针a. 

             2)int *a = new int(5) 作用同上,但是同时将整数赋值为5

    2.     开辟数组空间

              一维: int *a = new int[100];开辟一个大小为100的整型数组空间

              二维: int **a = new int[5][6]

              三维及其以上:依此类推.

             一般用法: new 类型 [初值]

    delete用法:

              1. int *a = new int;

                   delete a;   //释放单个int的空间

              2.int *a = new int[5];

                   delete [] a; //释放int数组空间

              要注意new和delete是一对,new[]和delete[]是一对。

              要访问new所开辟的结构体空间,无法直接通过变量名进行,只能通过赋值的指针进行访问.

              用new和delete可以动态开辟,撤销地址空间.在编程序时,若用完一个变量(一般是暂时存储的数组),下次需要再用,但却又想省去重新初始化的功夫,可以在每次开始使用时开辟一个空间,在用完后撤销它.




    
    
    
    
    
    
    
    
    更多相关内容
  • 在C中,如何知道动态分配是否成功?常见内存错误与对策 业界语句说法,是否了解内存管理机制,是辨别C/C++程序员和其他的高级语言程序员的重要区别。作为C/C++中的最重要的特性,指针及动态内存管理在给编程带来极大...

    在C中,如何知道动态分配是否成功?


    业界语句说法, 是否了解内存管理机制,是辨别C/C++程序员和其他的高级语言程序员的重要区别。作为C/C++中的最重要的特性,指针及动态内存管理在给编程带来极大的灵活性的同时,也给开发人员带来了许多困扰。

    在 C 编程语言中,我们使用 malloc 函数动态地(在堆上)分配内存。传递 malloc 一个 size 参数,该参数对应于所需的字节数。如果无法分配内存,该函数将返回指向已分配内存的指针或 NULL 指针。
    或许你曾经这样想过,编写一个分配1T内存的程序,然后尝试写入这个新分配的内存:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
      size_t large = 1099511627776;
      char *buffer = (char *)malloc(large);
      if (buffer == NULL) {
        printf("error!\n");
        return EXIT_FAILURE;
      }
      printf("Memory allocated\n");
      for (size_t i = 0; i < large; i += 4096) {
        buffer[i] = 0;
      }
      free(buffer);
      return EXIT_SUCCESS;
    }
    

    在运行和编译这个程序之后,您可能期望得到消息“ error!”,在这种情况下,程序立即终止… 或者你可能会看到“Memory allocated”(如果1T的内存是可用的) ,在这种情况下,程序应该成功终止。

    在 macOS/clang 和 Linux/GCC 下,我发现程序输出“Memory allocated”,然后崩溃。

    发生什么事了?
    Malloc 调用确实分配内存,但它几乎肯定会分配“virtual memory”。在剩余的地方,可能根本没有分配任何物理内存。系统只是留出地址空间用于内存分配。只有当您尝试使用内存时,物理分配才会发生。

    然后它可能会失败。
    这意味着在调用 malloc 时,检查内存分配是否成功可能没有您想象的那么有用。
    当人们询问一个程序使用了多少内存时,这也会导致混淆。将对 malloc 的调用加起来是错误的,因为您得到了虚拟内存的使用情况。如果使用系统级工具报告进程的内存使用情况,则应查看实际内存使用情况。下面是目前我见过笔记本电脑上几个进程的内存使用情况:
    在这里插入图片描述
    动态内存以页(例如,4 kB)分配,它通常比虚拟内存小得多。执行“ malloc (x)”与占用 x 字节的物理内存不同。
    因此,一般来说,在依赖 malloc 时,很难知道分配是否成功。你只有在写入和读入新分配的内存时才能知道。

    对于使用共享库的每个进程,即使它们占用相同的只读或写时复制内存页,也可能在实际内存和虚拟内存中计算共享库的数量,而内存映射文件可能在虚拟内存中全部计算,即使只读取了文件的一小部分,在Linux上,当您的进程试图真正访问过度分配的虚拟内存时,内存不足”杀手“可能会选择“杀死”一个其他不同的进程,而C共享库可能不会真正释放您释放的内存(),因为在下次尝试malloc()时,保留内存会更快,以避免碰到内核,这些东西都不是一成不变的标准,所以我所知道的一切,或者我刚刚叙述起的一切,可能已经过时多年了…

    至少堆分配最终都会通过一些瓶颈 API,因此您可以通过 LD_PRELOAD 来获得对内存使用优化的模糊处理,以拦截和统计这些调用。

    当我的博客文章没有真正回答博客标题中提出的问题时,这可能令你失望。我们能得到的最佳答案可能真的是:如果以后没有任何崩溃,那么分配一定在某种意义上成功了?我认为这就是为什么一些嵌入式系统的人最终试图将所有内容都保留在堆栈中或静态全局变量中。

    常见内存错误与对策

    常见的内存错误及其对策如下:

    • 内存分配未成功,却使用了它

    编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。

    • 内存分配虽然成功,但是尚未初始化就引用它

    犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

    • 内存分配成功并且已经初始化,但操作越过了内存的边界

    例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。

    • 忘记了释放内存,造成内存泄露

    含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。

    动态内存的申请与释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误(new/delete同理)。

    • 释放了内存却继续使用它

    有三种情况:

    (1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

    (2)函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

    (3)使用free或delete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。

    关于malloc()和free()函数的底层源码解析,推荐一个大佬的深度解析;

    2万字|30张图带你领略glibc内存管理精髓(因为OOM导致了上千万损失)

    展开全文
  • 动态内存分配与静态内存分配

    千次阅读 2021-05-22 19:02:34
    静态内存是指程序开始运行时,由编译器自动分配和释放空间。程序中的各种变量,在程序编译时都需要分配空间,当函数调用完,空间自动释放。此时**用户不需要关心空间的申请与释放问题。**通常都储存在栈中。 例如: ...

    一.静态内存管理

    静态内存是指程序开始运行时,由编译器自动分配和释放空间。程序中的各种变量,在程序编译时都需要分配空间,当函数调用完,空间自动释放。此时**用户不需要关心空间的申请与释放问题。**通常都储存在栈中。
    例如:

    int i=20//在栈上开辟4字节的空间
    char a[10]={0};//在栈上开辟10字节的空间
    

    二.动态内存管理

    1.为什么存在动态内存管理

    静态内存管理由两个特点:

    1.空间开辟的大小时固定的
    2.数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
    

    而动态内存分配,可以很好的解决上面的问题,当需要开辟的空间不确定时,静态内存管理就不好定义变量的大小,这是使用动态内存,可以得到一个变常数组,大小可以由自己开辟。并且动态内存可以提供大块的内存。
    注意:动态内存是在堆上开辟的,必须由程序员申请释放

    2.动态内存函数的介绍

    1).malloc

    • 表现形式
      在这里插入图片描述
      参数为需要开辟空间的大小,返回值为开辟空间的起始地址。
    • 特点
      1.如果开辟成功,则返回一个指向开辟好的空间的首地址。
      2.如果开辟不成功,则返回一个空指针(NULL),因此malloc的返回值一定要做判断是否为空。
      3.返回值为void*类型,所以具体使用时类型要有使用者决定,意思就是要强转类型。
      4.如果参数size为0,malloc的行为是标准还是未定义的,取决于编译器。
      注意:后面需要手动释放空间
    • 使用
    	int a = 0;
    	scanf("%d", &a);
    	char *p = (char *)malloc(sizeof(char)*a);//在堆上开辟a字节的空间,
    	//用指针p指向在堆上开辟空间的首地址。p在栈上开辟空间
    

    2.calloc

    • 表现形式
      在这里插入图片描述
      返回值为void* 开辟空间的首地址,参数为num 个空间,size为空间的大小。
    • 作用
      作用与malloc作用相同,区别在于开辟num个大小为size的空间,并将空间的每一个字节初始化为0。
      注意使用后判定指针是否为空。并且后面需要手动释放空间
    • 使用
    int *p=(int *)calloc(10,sizeof(int));//在堆上开辟40字节的空间,并且初始化为0。
    

    3.realloc

    • 表现形式
      在这里插入图片描述
      返回值为void* 开辟空间的首地址,参数ptr为要调整的动态内存的首地址。size为调整之后的新大小。
    • 作用
      有时我们发现之前申请的空间太小了或者太大了,realloc函数可以做到对动态开辟内存大小的调整,其它作用与malloc作用相同。
      注意:使用后判断地址是否为空。并且后面需要手动释放空间
      realloc函数调整地址有三种情况:
      1.开辟空间过大,返回地址与需要调整空间的地址相同,只要将空间缩小。
      在这里插入图片描述
      2.开辟空间过小,原有空间之后有足够大的空间,返回地址与需要调整空间的首地址相同,只要将空间向后扩大。
      在这里插入图片描述
      3.开辟空间过小,原有空间之后没有足够大的空间,需要在另外足够大的空间处重新开辟空间,返回地址与要调整地址不同。
      在这里插入图片描述
    • 使用:
    int *prt=(int *)malloc(sizeof(int)*10);
    int *p=(int *)realloc(prt.100);
    

    注意:

    prt=realloc(prt,100);//错误
    

    如果prt空间小了,要调整大一点,如果realloc函数调整后,返回地址与原来不相同,prt指向新地址,但是prt原来的小地址空间还在,如果prt指向新地址,原来开辟的小的地址空间就找不到了。造成内存泄露。

    4.free

    • 表现形式
      在这里插入图片描述
      不需要返回值,参数为动态申请空间的首地址。
    • 作用:
      用来释放动态开辟的内存
      1.如果prt指针指向的空间不是动态开辟的,那么free行为是未定义的。
      2.如果参数prt是NULL指针,则free函数什么都不做。

    5.动态开辟空间代码的标准写法

    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	int a = 0;
    	scanf("%d", &a);
    	int *p = (int *)malloc(sizeof(int)*a);
    	if (p == NULL){//判断空间是否开辟成功
    		printf("malloc error\n");
    		return 1;
    	}
    	for (int i = 0; i < a; i++){
    		*(p + i) = 0;
    	}
    	free(p);//是否开辟空间
    	p = NULL;//防止成为野指针
    	return 0;
    }
    

    注意点:
    1. 动态开辟的空间必须free释放,不然会导致内存泄露
    2.free函数只是切断了指针与堆(开辟的内存)之间的联系,指针里存的内容没变,只是不能指向堆了。为了防止指针变成野指针,最后要让指针指向NULL。
    3.删除数据不是将数据全部清空,只是设成了无效。

    展开全文
  • C/C++内存分配管理

    万次阅读 多人点赞 2018-08-13 14:57:23
    内存分配及管理 1.内存分配方式 在C++中内存分为5个区,分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。 堆:堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言...

    内存分配及管理

    1.内存分配方式

    在C++中内存分为5个区,分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。

    堆:堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。

    栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

    自由存储区:自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。

    全局/静态存储区:这块内存是在程序编译的时候就已经分配好的,在程序整个运行期间都存在。例如全局变量,静态变量。

    常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量(const),不允许修改。

    2.常见的内存错误及解决方法

      a.内存分配未成功,却使用了它。

     解决方法:在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。如果是用new来申请内存,申请失败是会抛出异常,所以应该捕捉异常来进行放错处理。(感谢@ melonstreet指出)。

      b.内存分配虽然成功,但是尚未初始化就引用它。

       解决方法:尽管有时候缺省时会自动初始化,但是无论创建什么对象均要对其进行初始化,即便是赋零值也不可省略,不要嫌麻烦。

      c.内存分配成功,但越界访问。

     解决方法:对数组for循环时要把握边界,否则可能会导致数组越界。

      d.忘记了释放内存,导致内存泄漏。

     解决方法:动态内存的申请和释放必须配对,new-delete和malloc-free且使用次数必须相同。
      c.已经释放内存却仍然使用它。

     有三种情况:

           1.程序中对象的关系过于复杂,难以搞清哪个对象是否已经释放了内存。

                      2.函数中return写错,返回了指向栈中的指针或引用。

           3. free或delete后,没有将指针设为NULL,产生”野指针”。

    new与malloc的10点区别

    1. 申请的内存所在位置

    new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。

    那么自由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。

    特别的,new甚至可以不为对象分配内存!定位new的功能可以办到这一点:

    new (place_address) type

    place_address为一个指针,代表一块内存的地址。当使用上面这种仅以一个地址调用new操作符时,new操作符调用特殊的operator new,也就是下面这个版本:

    void * operator new (size_t,void *) //不允许重定义这个版本的operator new

    这个operator new不分配任何的内存,它只是简单地返回指针实参,然后右new表达式负责在place_address指定的地址进行对象的初始化工作。

    2.返回类型安全性

    new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
    类型安全很大程度上可以等价于内存安全,类型安全的代码不会试图方法自己没被授权的内存区域。关于C++的类型安全性可说的又有很多了。

    3.内存分配失败时的返回值

    new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。
    在使用C语言时,我们习惯在malloc分配内存后判断分配是否成功:

    int *a  = (int *)malloc ( sizeof (int ));
    if(NULL == a)
    {
        ...
    }
    else 
    {
        ...
    }

    从C语言走入C++阵营的新手可能会把这个习惯带入C++:

    int * a = new int();
    if(NULL == a)
    {
        ...
    }
    else
    {   
        ...
    }

    实际上这样做一点意义也没有,因为new根本不会返回NULL,而且程序能够执行到if语句已经说明内存分配成功了,如果失败早就抛异常了。正确的做法应该是使用异常机制:

    try
    {
        int *a = new int();
    }
    catch (bad_alloc)
    {
        ...
    }

     

    4.是否需要指定内存大小

    使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸。

    class A{...}
    A * ptr = new A;
    A * ptr = (A *)malloc(sizeof(A)); //需要显式指定所需内存大小sizeof(A); 

    当然了,我这里使用malloc来为我们自定义类型分配内存是不怎么合适的,请看下一条。

    5.是否调用构造函数/析构函数

    使用new操作符来分配对象内存时会经历三个步骤:

    • 第一步:调用operator new 函数(对于数组是operator new[])分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象。
    • 第二步:编译器运行相应的构造函数以构造对象,并为其传入初值。
    • 第三部:对象构造完成后,返回一个指向该对象的指针。

    使用delete操作符来释放对象内存时会经历两个步骤:

    • 第一步:调用对象的析构函数。
    • 第二步:编译器调用operator delete(或operator delete[])函数释放内存空间。

    总之来说,new/delete会调用对象的构造函数/析构函数以完成对象的构造/析构。而malloc则不会。如果你不嫌啰嗦可以看下我的例子:

    class A
    {
    public:
        A() :a(1), b(1.11){}
    private:
        int a;
        double b;
    };
    int main()
    {
        A * ptr = (A*)malloc(sizeof(A));
        return 0;
    }

    在return处设置断点,观看ptr所指内存的内容:

    可以看出A的默认构造函数并没有被调用,因为数据成员a,b的值并没有得到初始化,这也是上面我为什么说使用malloc/free来处理C++的自定义类型不合适,其实不止自定义类型,标准库中凡是需要构造/析构的类型通通不合适。

    而使用new来分配对象时:

    int main()
    {
        A * ptr = new A;
    }

    查看程序生成的汇编代码可以发现,A的默认构造函数被调用了:

    6.对数组的处理

    C++提供了new[]与delete[]来专门处理数组类型:

        A * ptr = new A[10];//分配10个A对象

    使用new[]分配的内存必须使用delete[]进行释放:

        delete [] ptr;

    new对数组的支持体现在它会分别调用构造函数函数初始化每一个数组元素,释放对象时为每个对象调用析构函数。注意delete[]要与new[]配套使用,不然会找出数组对象部分释放的现象,造成内存泄漏。

    至于malloc,它并知道你在这块内存上要放的数组还是啥别的东西,反正它就给你一块原始的内存,在给你个内存的地址就完事。所以如果要动态分配一个数组的内存,还需要我们手动自定数组的大小:

    int * ptr = (int *) malloc( sizeof(int)* 10 );//分配一个10个int元素的数组

    7.new与malloc是否可以相互调用

    operator new /operator delete的实现可以基于malloc,而malloc的实现不可以去调用new。下面是编写operator new /operator delete 的一种简单方式,其他版本也与之类似:

    void * operator new (sieze_t size)
    {
        if(void * mem = malloc(size)
            return mem;
        else
            throw bad_alloc();
    }
    void operator delete(void *mem) noexcept
    {
        free(mem);
    }

    8.是否可以被重载

    opeartor new /operator delete可以被重载。标准库是定义了operator new函数和operator delete函数的8个重载版本:

    //这些版本可能抛出异常
    void * operator new(size_t);
    void * operator new[](size_t);
    void * operator delete (void * )noexcept;
    void * operator delete[](void *0)noexcept;
    //这些版本承诺不抛出异常
    void * operator new(size_t ,nothrow_t&) noexcept;
    void * operator new[](size_t, nothrow_t& );
    void * operator delete (void *,nothrow_t& )noexcept;
    void * operator delete[](void *0,nothrow_t& )noexcept;
    

    我们可以自定义上面函数版本中的任意一个,前提是自定义版本必须位于全局作用域或者类作用域中。太细节的东西不在这里讲述,总之,我们知道我们有足够的自由去重载operator new /operator delete ,以决定我们的new与delete如何为对象分配内存,如何回收对象。

    而malloc/free并不允许重载

    9. 能够直观地重新分配内存

    使用malloc分配的内存后,如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来的内存区域。

    new没有这样直观的配套设施来扩充内存。

    10. 客户处理内存分配不足

    在operator new抛出异常以反映一个未获得满足的需求之前,它会先调用一个用户指定的错误处理函数,这就是new-handler。new_handler是一个指针类型:

    namespace std
    {
        typedef void (*new_handler)();
    }

    指向了一个没有参数没有返回值的函数,即为错误处理函数。为了指定错误处理函数,客户需要调用set_new_handler,这是一个声明于的一个标准库函数:

    namespace std
    {
        new_handler set_new_handler(new_handler p ) throw();
    }

    set_new_handler的参数为new_handler指针,指向了operator new 无法分配足够内存时该调用的函数。其返回值也是个指针,指向set_new_handler被调用前正在执行(但马上就要发生替换)的那个new_handler函数。

    对于malloc,客户并不能够去编程决定内存不足以分配时要干什么事,只能看着malloc返回NULL。

    总结

    将上面所述的10点差别整理成表格:

    特征new/deletemalloc/free
    分配内存的位置自由存储区
    内存分配成功的返回值完整类型指针void*
    内存分配失败的返回值默认抛出异常返回NULL
    分配内存的大小由编译器根据类型计算得出必须显式指定字节数
    处理数组有处理数组的new版本new[]需要用户计算数组的大小后进行内存分配
    已分配内存的扩充无法直观地处理使用realloc简单完成
    是否相互调用可以,看具体的operator new/delete实现不可调用new
    分配内存时内存不足客户能够指定处理函数或重新制定分配器无法通过用户代码进行处理
    函数重载允许不允许
    构造函数与析构函数调用不调用
    展开全文
  • ,包含在库函数 stdlib.h中,作用是在内存的堆区分配一个大小为size的连续空间,如果分配内存成功,函数返回新分配内存的首地址,否则,返回NULL,注意:鉴于上述这点,一般在写程序需要判断分配内存是否成功,如下...
  • ,包含在库函数 stdlib.h中,作用是在内存的堆区分配一个大小为size的连续空间,如果分配内存成功,函数返回新分配内存的首地址,否则,返回NULL,注意:鉴于上述这点,一般在写程序需要判断分配内存是否成功,如下...
  • ,包含在库函数 stdlib.h中,作用是在内存的堆区分配一个大小为size的连续空间,如果分配内存成功,函数返回新分配内存的首地址,否则,返回NULL,注意:鉴于上述这点,一般在写程序需要判断分配内存是否成功,如下...
  • 动态内存分配和静态内存分配 1.什么是静态内存分配和动态内存分配 主要体现在时间和空间的不同。静态内存分配是在程序执行之前,由编译器在程序编译阶段实现的,动态内存分配是在程序执行时由程序完成。。静态内存...
  • 文章目录内存分配方式堆与栈的区别指针与数组指针与数组区别指针参数传递内存 本文来源《高质量C++编程》第7章 内存分配方式 从静态存储区域分配 1.在程序编译时就已经分配好 2.在程序整个运行期间都存在 3.全局...
  • C语言内存分配

    千次阅读 2022-03-25 10:36:29
    一,内存分配 1,内存分配的类型: 在C/C++中内存分为5个区,分别为栈区、堆区、全局/静态存储区、常量存储区、代码区。 静态内存分配:编译时分配。包括:全局、静态全局、静态局部三种变量。 动态内存分配:运行...
  • Java对象内存分配流程

    千次阅读 2022-08-08 17:49:20
    Java对象内存分配流程
  • 用指针操作和访问数组一样,但区别是首先要判断是否分配成功:p = NULL即为失败,否则成功。 3.动态分配的空间在堆区。 二、动态分配内存所用的函数 new:进行动态内存申请 delete:进行动态内存释放(动态分配的...
  • 链表的C语言实现(含动态内存分配)

    千次阅读 2021-05-22 14:03:05
    转自:http://blog.csdn.net/21aspnet/article/details/146968链表的C语言实现(含动态内存分配)上 链表的C语言实现之动态内存分配一、为什么用动态内存分配但我们未学习链表的时候,如果要存储数量比较多的同类型或...
  • 【C语言】动态内存分配

    千次阅读 多人点赞 2022-01-28 17:20:06
    动态内存分配就那么回事儿!
  • java对象内存分配

    千次阅读 2022-01-27 23:16:35
    如果可以在栈上分配,就直接在栈上分配,不行就会进行TLAB分配,再不行就判断是否是大对象,大对象直接进入老年代,再不行就分配到eden区,eden若是空间不够,就会进行一次MinorGC。 大对象 顾名思义就是很大的对象...
  • 动态内存分配 (详解版)

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

    2022-07-21 08:38:45
    内存管理函数可以按需要动态的分配内存空间,也可把不再使用的空间回收再次利用。
  • C语言 动态内存分配函数目录一、为什么要用动态内存二、如何使用动态内存三、malloc() 与 calloc()、realloc()联系四、筛选法找素数前言一、为什么要用动态内存在c中开辟内存空间有两种方式:1、静态开辟内存2、动态...
  • C语言中的动态内存分配

    千次阅读 2022-02-12 16:43:29
    大家好,今天简单讲一讲C语言中的动态内存分配。 截至目前,我们所使用的变量似乎都是在栈区(如局部变量、函数形参)和静态区(如全局变量、static修饰的变量),但是我们很早就知道除了栈区、静态区,在内存中...
  • 判断用new申请内存是否成功

    万次阅读 2014-07-04 10:24:23
    char* p=NULL;//最好初始化为NULL p = new char[nSize]; if(p == NULL) exit(); .... delete[] p; p = NULL; //删除马上赋值为NULL
  • 摘要:C语言中比较重要的就是指针,它可以用来链表操作,谈到链表,很多时候为此分配内存采用动态分配而不是静态分配
  • C++ 在栈上分配内存

    千次阅读 2020-01-02 09:44:09
    C语言跟内存分配方式 (1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。 (2) 在栈上创建。在执行函数时,函数内局部变量的存储...
  • C语言之动态分配内存空间

    千次阅读 2021-05-26 03:05:09
    动态分配内存为什么需要动态分配内存:1.存储的数据 需要延长生命周期2...数组确定大小是为了分配内存空间//如果使用指针变量接收数据//必须先为这个指针变量分配一片指向的内存空间//有内存空间 才能存数据//使用ma...
  • C语言动态内存分配函数

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

    千次阅读 2021-08-12 20:10:26
    案例1:新生代无法分配内存,晋升到老年代,且担保成功 结果1:使用Serial收集器 结果2:使用ParallelScavenge收集器 案例2:Parallel Scavenge晋升条件:当需要分配内存小于等于Eden区内存的一半,就有可能让...
  • 内存分配算法(FF、BF、MF)

    万次阅读 多人点赞 2019-05-09 17:58:48
    最近在学习操作系统内存分配方面的知识点,小小的总结一下知识点。 根据进程的实际需要,动态的为之分配内存空间。在实现动态分区分配时,将涉及到分区分配中所用到的数据结构、分区分配算法和分区的分配与回收...
  • 内存耗尽和处理方式,指针为NULL的情况,内存分配失败 堆内存耗尽和处理方式: 如果在申请动态内存时找不到足够大的内存块,malloc和new都会返回空指针宣告内存申请失败。 因此只需要用if(NULL == p)判断一下就可以...
  • c/c++指针详解(二)----内存分配

    千次阅读 2016-11-21 19:09:45
    1、内存分配的三种方式: 1)、从静态存储区分配。数据的内存在程序编译时已经被分配,该内存在整个运行期间长期驻留,不会被释放;程序结束时,由操作系统自动释放。这类数据包括静态数据和全局数据。 2)、从栈...
  • js数组内存分配

    千次阅读 2021-02-22 10:56:12
    V8中对数组做了一层封装,使其有两种实现方式:快数组和慢数组,快数组底层是连续内存,通过索引直接定位,慢数组底层是哈希表,通过计算哈希值来定位。两种实现方式各有特点,有各自的使用情况,也会相互转换。 一...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 181,396
精华内容 72,558
热门标签
关键字:

内存分配后必须判断是否成功