美文网首页
C语言的malloc

C语言的malloc

作者: Mr_Bluyee | 来源:发表于2018-09-05 14:56 被阅读31次

    为什么C语言要有malloc

    malloc就是memory allocate动态分配内存,malloc的出现时为了弥补静态内存分配的缺点,静态分配内存有如下缺点:

    1、比如说,传统的一维数组,如int a[5],使用传统的一维数组需要事先指定数组的长度,而且数组的长度必须是一个常量(宏定义的 常量)

    2、传统数组(静态分配),不能手动释放,只能等待系统释放,静态分配的变量在该函数内运行的时候有效,当静态分配的变量所在函数运行完之后,该内存会自动释放。静态分配的内存,是在栈中分配的,其实在C语言中的函数调用也是通过栈来实现的,栈这种数据结构的一个特点就是(先进后出),所以,在调用函数的时候,都是先压入栈中,然后,再从最上面的函数开始执行,最后,执行到main函数结束。动态分配通过malloc分配,是在堆中分配的,堆不是一种数据结构,它是一种排序方式,堆排序。

    3、传统数组的长度一旦定义之后,就不能更改,比如说,如果我有一个业务在这之前给分配的大小为100,但是,我现在由于业务数量的增长,原来的大小就无法满足。

    4、静态分配不能跨函数调用,就是无法在另一个函数中,来管理一个函数中的内存。静态分配,只在当前函数有效,当,静态分配所在的函数运行完之后,该变量就不能被其他的函数所调用。

    malloc是什么

    malloc其实就是一个可以动态分配内存的函数,从而可以很好的弥补上面静态分配的缺点。

    malloc怎么用

    1、使用malloc函数的时候,需要包含一个头文件#include <malloc.h>

    2、malloc函数只接受一个形参如,int *p = (int )malloc(sizeof(int)).先来解释下这句话的含义,int p代表一个以int类型地址为内容的指针变量,p这个变量占4个字节(某些计算机),这个p变量是静态分配的一个变量。

    在某些计算机的前提下,指针变量所占的大小都是一样的,无论是char* 还是long *,因为,这些指针变量里面存放的是一个8位16进制的地址,所以占四个字节,当然这些都是在某些计算机的前提下,并不是所有的都是这样的。

    说道地址的话,就和计算机的地址总线有关,如果计算机的地址总线是32根,每根地址总线只有两种状态(1或0),32根地址线的话,如果全为1的话,刚好就是一个8位十六进制,一位十六进制等于四个二进制(2^4=16)。32根地址总线可以 表示210*2102^102^2种状态,可以表示的最大内存为4G,也就是说32根地址总线(也就是四个字节 的指针变量)最大可以表示4G内存。

    malloc函数会返回开辟空间的首地址,加(int *)的目的是让计算机知道,如何去划分这个开辟的空间,因为char、int 、long这些类型的字节大小是不一样的,我们知道了首地址,还要知道是以几个字节为单元。所以,这句话一共开辟了8个字节(某些计算机上),这也是为什么我写sizeof(int),而不是直接写4的原因。

    3、malloc开辟空间所返回的首地址是动态分配的。

    malloc使用需要注意的地方

    1、申请了内存空间后,必须检查是否分配成功。

    2、当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。

    3、这两个函数应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。

    4、虽然malloc()函数的类型是(void *),任何类型的指针都可以转换成(void *),但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查。

    malloc从哪里得来了内存空间

    1、malloc()到底从哪里得到了内存空间?答案是从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

    2、什么是堆:堆是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程 初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。

    什么是栈:栈是线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立。每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

    通过上面对概念的描述,可以知道:

    栈是由编译器自动分配释放,存放函数的参数值、局部变量的值等。操作方式类似于数据结构中的栈。堆一般由程序员分配释放,若不释放,程序结束时可能由OS回收。注意这里说是可能,并非一定。所以我想再强调一次,记得要释放!
    举个例子,如果你在函数上面定义了一个指针变量,然后在这个函数里申请了一块内存让指针指向它。实际上,这个指针的地址是在栈上,但是它所指向的内容却是在堆上面的!千万不要认为函数返回,函数所在的栈被销毁指针也跟着销毁,申请的内存也就一样跟着销毁了!这绝对是错误的!因为申请的内存在堆上,而函数所在的栈被销毁跟堆完全没有啥关系。

    free()到底释放了什么

    free()释放的是指针指向的内存,不是指针。指针是一个变量,只有程序结束时才被销毁。释放了内存空间后,原来指向这块空间的指针还是存在。
    野指针(wild pointer):
    野指针指的是还没有初始化的指针。严格地说,编程语言中每个指针在初始化前都是野指针。一般于未初始化时便使用指针就会产生问题。大多数的编译器都能检测到这一问题并警告用户。
    悬空指针(dangling pointer):
    当所指向的对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情况下该指针便称悬空指针。若操作系统将这部分已经释放的内存重新分配给另外一个进程,而原来的程序重新引用现在的悬空指针,则将产生无法预料的后果。因为此时悬空指针所指向的内存现在包含的已经完全是不同的数据。通常来说,若原来的程序继续往悬空指针所指向的内存地址写入数据,这些和原来程序不相关的数据将被损坏,进而导致不可预料的程序错误。这种类型的程序错误,不容易找到问题的原因,通常会导致段错误(Linux系统中)和一般保护错误(Windows系统中)。如果操作系统的内存分配器将已经被覆盖的数据区域再分配,就可能会影响系统的稳定性。

    无论是野指针还是悬空指针,都是指向无效内存区域(这里的无效指的是"不安全不可控")的指针。 访问"不安全可控"(invalid)的内存区域将导致"Undefined Behavior"。也就是说:任何可能都会发生。要么编译失败,要么执行得不正确(崩溃(e.g. segmentation fault)或者悄无声息地产生不正确的执行结果),或者偶尔会正确地产生程序员希望运行的结果。

    如何避免使用野指针和悬空指针

    对于野指针:养成在定义指针后且在使用之前完成初始化的习惯就好。
    对于悬空指针:一个避免这个错误的方法是在释放它的引用后将该指针的值重置为NULL。

    在子函数中调用malloc申请内存的方法

    方法一:函数返回
    将malloc得到的内存首地址通过函数的返回值返回到主函数。

    #include <stdio.h>
    #include <malloc.h>
    #include <string.h>
    char* test()
    {
        char *p;
        p = (char*)malloc(10 * sizeof(char));
        strcpy(p, "123456789" );
        return p;
    }
    void main()
    {
        char *str = NULL ;
        str = test();
        printf("%s\n", str);
        free(str);
    }
    

    方法二:二级指针
    将malloc得到的内存首地址通过二级指针返回到主函数。

    #include <stdio.h>
    #include <malloc.h>
    #include <string.h>
    void test(char **p)
    {
        *p = (char*)malloc(10 * sizeof(char));
        strcpy(*p, "123456789" );   
    }
    void main()
    {
        char *str = NULL ;
        test(&str);
        printf("%s\n", str);
        free(str);
    }
    

    常见误区:
    错误一:使用一级指针

    #include <stdio.h>
    #include <malloc.h>
    #include <string.h>
    void test(char *p) 
    {
        p = (char*)malloc(10 * sizeof(char));
        strcpy(*p, "123456789" );   
    }
    void main()
    {
        char *str = NULL ;
        test(str);
        printf("%s\n", str);
        free(str);
    }
    

    看上去合情合理,把malloc得的地址赋给指针p,这样我们传入的str就指向申请的内存了。但事实是,str的值并没有变化。我们可以先看下方的代码。

    #include <stdio.h>
    void test(char c)
    {
        c = 'B';
    }
    void main()
    {
        char ch = 'A' ;
        test(ch);
        printf("%c\n", ch);
    }
    

    调用test()后,主函数里面的ch值还是’A’,而不是’B’。这是因为在调用函数的时候,char c 事实上是被复制进函数内部的,函数内的操作不会影响到原值。 指针也是一样的道理。传入一个一级指针,只能修改它指向的数据,而不能修改它指向的地址。所以我们应该传入一个二级指针,这个指针指向一级指针。这样我们就能修改位于二级指针指向的数据,即一级指针指向的地址了。
    错误二:二级指针未指向存在的一级指针

    #include <stdio.h>
    #include <malloc.h>
    #include <string.h>
    void test(char **p)
    {
        *p = (char*)malloc(10 * sizeof(char));
        strcpy(*p, "123456789" );   
    }
    void main()
    {
        char **str = NULL ; //原代码:char *str = NULL;
        test(str);          //       test(&str);
        printf("%s\n", str);
        free(str);
    }
    

    为什么我使用了二级指针,仍然是错误的呢?对比下正确的代码,就一目了然了。正确代码中,通过对一级指针str进行取址,得到指向str的二级指针,在子函数中就可以操作str的值了。而错误代码中,二级指针的值为NULL,这样的话,子函数中操作的是地址为NULL的内存,这当然是不对的。

    以上内容整理于:
    C语言指针之二malloc的用法及详解
    C语言中 malloc函数用法
    C语言在子函数中调用malloc申请内存的方法

    相关文章

      网友评论

          本文标题:C语言的malloc

          本文链接:https://www.haomeiwen.com/subject/okyawftx.html