精华内容
下载资源
问答
  • 在C中动态分配内存的基本步骤有: 1,用malloc类的函数分配内存; 2,用这些内存支持应用程序 3,用free函数释放内存 二、动态内存分配函数  malloc :从堆上分配内存  realloc : 在之前分配的内存块的基础上,将内存...

    #include<stdio.h>

    /**
    在C中动态分配内存的基本步骤有:
    1,用malloc类的函数分配内存;
    2,用这些内存支持应用程序
    3,用free函数释放内存
    二、动态内存分配函数
        malloc :从堆上分配内存
        realloc : 在之前分配的内存块的基础上,将内存重新分配为更大或者更小的部分
        calloc: 从堆上分配内存并清零
        free:将内存块返回堆
    */
    void mainaa()
    {
        int *pi = (int*) malloc(sizeof(int));
        *pi = 5;
        printf("*pi:%d\n",*pi);
        free(pi);
    }
    //为字符串分配内存,将其初始化,并逐个字符打印字符串,然而每次迭代name都会增加1,最后name会指向字符串结尾的NUL字符,
    //分配内存的起始地址丢失了
    void mainbb()
    {
        //为10个双精度浮点数分配空间,需要80个字节
        //double *pd = (double*)malloc(NUMBER_OF_DOUBLES*sizeof(double));
        //以下程序只分配了10个字节
        const int NUMBER_OF_DOUBLES = 10;
        double *pd = (double*)malloc(NUMBER_OF_DOUBLES);

        //初始化静态或全局变量时不能调用函数,下面的代码声明一个静态变量,并试图用
        //malloc来初始化,这样会产生一个编译时错误消息
        //static int *pi = malloc(sizeof(int));

        char *name = (char*)malloc(strlen("Susan")+1);
        strcpy(name,"Susan");

        while(*name != 0){
            printf("%c",*name);
            name++;
        }
    }
    /**
        使用calloc函数
        calloc会在分配的同时清空内存,该函数的原型如下:void *calloc(size_t numElements,size_t elementSize);
        calloc函数会根据numElements和elementSize两个参数的乘积来分配内存,并返回一个指向内存的第一个字节的指针。如果不能分配内存
        则返回null,此函数最初用来辅助分配数组内存。
        如果numElements或elementSize为0,那么calloc可能返回空指针。如果calloc无法分配内存就会返回空指针,而且全局变量errno会设置为ENOMEM(内存不足),
        这是POSIX错误码,有的系统上可能没有
    **/
    void maincc()
    {
        //下面两端代码都是为pi分配了20字节,全部包含0
        //int *pi = calloc(5,sizeof(int));

        //int *pi = malloc(5*sizeof(int));
        //memset(pi,0,5*sizeof(int));

        /**
            memset函数会用某个值填充内存块,第一个参数是指向要填充的缓冲区的指针,第二个参数是填缓冲区的值,最后一个参数是要填充的字节数。
            如果内存需要清零可以使用calloc,不过执行calloc可能比执行malloc慢。cfree函数已经没用了。
        */
    }
    /**
        realloc函数
        realloc函数会重新分配内存,原型:void *realloc(void *ptr,size_t size);
        realloc函数返回指向内存块的指针。该函数接受两个参数,第一个参数是指向原内存块的指针,第二个是请求的大小。重新分配的块大小和第一个参数
        引用的块大小不同。返回值是指向重新分配的内存的指针。
        请求的大小可以比当前分配的字节数小或者大。如果比当前分配的小,那么多余的内存会还给堆,不能保证多余的内存会被清空。如果比当前分配的大,
        那么可能的话,就在紧挨着当前分配内存的区域分配新的内存,否则就会在堆的其他区域分配并把旧的内存复制到新区域。
        如果大小是0而指针非空,那么就释放内存。如果无法分配空间,那么原来的内存块就保持不变,不过返回的指针是空指针,且errno会设置为ENMOEM,
    **/
    void maindd()
    {
        /**
            下例使用两个变量为字符串分配内存。一开始分配16个字节,但只用到了前面的13个字节(12个十六进制数字外加null结束字符(0))
        */
        char *string1;
        char *string2;
        string1 = (char*)malloc(16);
        strcpy(string1,"0123456789AB");
        
        /**
            紧接着,用realloc函数指定一个范围更小的内存区域。然后打印这两个变量的地址和内容
        */
        string2 = realloc(string1,8);
        printf("string1 value:%p [%s]\n",string1,string1);
        printf("string2 value:%p [%s]\n",string2,string2);
    }
    /**
        alloca函数和变长数组
        alloca函数(微软为malloca)在函数的栈帧上分配内存。函数返回后会自动释放内存。若低层的运行时系统不基于栈,
        allocal函数会很难实现,所以这个函数时不标准的,如果应用程序需要可移植就尽量避免使用它。
        C99引入了变长数组(VLA),允许函数内部声明和创建其长度由变量决定的数组,比如:
        void compute(int size){
            char * buffer[size];
            ...
        }
        这意味着内存分配在运行时完成,且将内存作为栈帧的一部分来分配。另外,如果数组用到sizeof操作符,也是在运行时而不是编译时执行。
        这么做只会有一点小小的运行时开销。而且一旦函数退出,立即释放内存。因为我们没有用malloc这类函数来创建数组,所以不应该用free函数来
        释放它。alloca函数也不应该返回指向数组所在内存的指针,但是可以解决。
        VLA的长度不能改变,一经分配其长度就固定了。
    **/

    /**
        动态内存分配技术
        1,资源获取即初始化
        资源获取即初始化(Resource Acquisition Is Initialization,RAII)是Bjarne Stroustrup发明的技术,可以用来解决C++中资源的分配和释放。
        即使有异常发生,这种技术也能保证资源的初始化和后续的释放。分配的资源最终总是会得到释放。
        有好几种方法可以在C中使用RAII。GNU编译器提供了非标准的扩展来支持这个特性,通过演示如何在一个函数中分配内存然后释放可以说明这种
        扩展。一旦变量超出作用域会自动触发释放过程。
        GNU的扩展需要用到RAII_VARIABLE宏,它声明一个变量,然后给变量关联如下属性
            1,一个类型。
            2,创建变量时执行的函数。
            3,变量超出作用域时执行的函数。
        这个宏如下所示:
            #define RAII_VARIABLE(vartype,varname,initval,dtor)\
                void _dtor_ ## varname (vartype * v){dtor(*v);}\
                vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval)
        在下例中,我们将name变量声明为字符指针。创建它时会执行malloc函数,为其分配32字节。当函数结束时,name超出作用域就会执行free函数:
        void raiiExample(){
            RAII_VARIABLE(char*,name,(char*)malloc(32),free);
            strcpy(name,"RAII Example");
            printf("%s\n",name);
        }
        函数执行后会打印"RAII_Example"字符串。不用GNU扩展也可以达到类似效果。
        2、使用异常处理函数
        另外一种处理内存释放的方法是利用异常处理。尽管异常处理不属于标准C,但如果可以使用它且不考虑移植问题,它会很有用。下面说明利用
        Microsoft Visua Studio版的C语言的方法。
        这里的try块包含任何可能在运行时抛出异常的语句。不管有没有异常抛出,都会执行finally块,因此也一定会执行free函数。
        void exceptionExample(){
            int *pi = NULL;
            __try{
                pi = (int*)malloc(sizeof(int));
                *pi = 5;
                printf("%d\n",*pi);
            }
            __finally{
                free(pi);
            }
        }
    */

    展开全文
  • 动态分配内存

    千次阅读 2016-08-23 16:26:57
    ☆动态分配内存 所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,...

    动态分配内存

    所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不象数组静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。

    C/C++定义了4个内存区间:代码区,全局变量与静态变量区,局部变量区即栈区,动态存储区,即堆区或自由存储区。

    堆的概念:通常定义变量(或对象),编译器在编译时都可以根据该变量(或对象)的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间。这种内存分配称为静态存储分配;有些操作对象只在程序运行时才能确定,这样编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为动态存储分配。所有动态存储分配都在堆区中进行。当程序运行到需要一个动态分配的变量或对象时,必须向系统申请取得堆中的一块所需大小的存贮空间,用于存贮该变量或对象。当不再使用该变量或对象时,也就是它的生命结束时,要显式释放它所占用的存贮空间,这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源。

    在使用数组的时候,总有一个问题困扰着我们:数组应该有多大?在很多的情况下,你并不能确定要使用多大的数组,比如上例,你可能并不知道我们要定义的这个数组到底有多大,那么你就要把数组定义得足够大。这样,你的程序在运行时就申请了固定大小的你认为足够大的内存空间。即使你知道你想利用的空间大小,但是如果因为某种特殊原因空间利用的大小有增加或者减少,你又必须重新去修改程序,扩大数组的存储范围。这种分配固定大小的内存分配方法称之为静态内存分配。但是这种内存分配的方法存在比较严重的缺陷,特别是处理某些问题时:在大多数情况下会浪费大量的内存空间,在少数情况下,当你定义的数组不够大时,可能引起下标越界错误,甚至导致严重后果。

    我们用动态内存分配就可以解决上面的问题. 所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。

    从以上动、静态内存分配比较可以知道动态内存分配相对于静态内存分配的特点:

    1、不需要预先分配存储空间;

    2、分配的空间可以根据程序的需要扩大或缩小。

    要实现根据程序的需要动态分配存储空间,就必须用到malloc函数.

    malloc函数的原型为:void *malloc (unsigned int size) 其作用是在内存的动态存储区中分配一个长度为size的连续空间。其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针。还有一点必须注意的是,当函数未能成功分配存储空间(如内存不足)就会返回一个NULL指针。所以在调用该函数时应该检测返回值是否为NULL并执行相应的操作。

    分配方法:
    1.生成变量
    (1)new可用来生成动态无名变量

    如 int *p=new int;

    int *p=new int [10]; //动态数组的大小可以是变量或常量;而一般直接声明数组时,数组大小必须是常量

    又如:

    int *p1;

    double *p2;

    p1=new int⑿;

    p2=new double [100];

    l 分别表示动态分配了用于存放整型数据的内存空间,将初值12写入该内存空间,并将首地址值返回指针p1;

    l 动态分配了具有100个双精度实型数组元素的数组,同时将各存储区的首地址指针返回给指针变量p2;

    对于生成二维及更高维的数组,应使用多维指针

    以二维指针为例

    int **p=new int* [row]; //row是二维数组的行,p是指向一个指针数组的指针

    for(int i=0; i<row; i++)

    p=new int [col];     //col是二维数组的列,p是指向一个int数组的指针

    删除这个二维数组

    for(int i = 0; i < row;i++)

    delete []p; //先删除二维数组的列

    delete []p;

    使用完动态无名变量后应该及时释放,要用到 delete 运算符

    delete p; //释放单个变量(或者free(p);   释放内存)

    delete [ ] p; //释放数组变量(不论数组是几维)

    相比于一般的变量声明,使用new和delete 运算符可方便的使用变量。

    2.malloc函数

    原型:extern void *malloc(unsigned int num_bytes);

    头文件:在TC2.0中可以用malloc.h或 alloc.h (注意:alloc.h 与 malloc.h 的内容是完全一致的),而在Visual C++6.0中可以用malloc.h或者stdlib.h

    功能:分配长度为num_bytes字节的内存块

    返回值:如果分配成功则返回指向被分配内存的指针(此存储区中的初始值不确定),否则返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。函数返回的指针一定要适当对齐,使其可以用于任何数据对象。

    说明:关于该函数的原型,在旧的版本中malloc返回的是char型指针,新的ANSIC标准规定,该函数返回为void型指针,因此必要时要进行类型转换。

    名称解释:malloc的全称是memory allocation,中文叫动态内存分配,当无法知道内存具体位置的时候,想要绑定真正的内存空间,就需要用到动态的分配内存。

    常见错误:

    使用动态内存分配的程序中,常常会出现很多错误。

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

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

    3. 释放并非动态分配的内存

    4. 试图释放一块动态分配的内存的一部分以及一块内存被释放之后被继续使用。

    说明:

    1. 动态分配最常见的错误就是忘记检查所请求的内存是否成功分配。

    2. 动态内存分配的第二大错误来源是操作内存时超出了分配内存的边界。

    3. 当你使用free时,可能出现各种不同的错误。

    1>传递给free的指针必须是一个从malloc、calloc或realloc函数返回的指针。

    2>传递给free函数一个指针,让它释放一块并非动态分配的内存可能导致程序立即终止或在晚些时候终止。

    3>试图释放一块动态分配内存的一部分也有可能引起类似问题。

    4>不要访问已经被free函数释放了的内存。假定对一个指向动态分配的内存的指针进行了复制,而且这个指针的几份拷贝分散于程序各处。你无法保证当你使用其中一个指针时它所指向的内存是不是已被另一个指针释放。还要确保程序中所有使用这块内存的地方在这块内存释放之前停止对它的使用。

    5>当动态分配的内存不再需要使用时,应该被释放,这样可以被重新分配使用。分配内存但在使用完毕后不释放将引起内存泄漏(memory leak)。

    总结:

    1.当数组被声明时,必须在编译时知道它的长度。动态内存分配允许程序为一个长度在运行时才知道的数组分配内存空间。

    2.malloc和calloc函数都用于动态分配一块内存,并返回一个指定该块内存的指针。

    1>malloc的参数就是需要分配的内存的字节数。

    2>calloc的参数是需要分配的元素个数和每个元素的长度。calloc函数在返回前把内存初始化为零。malloc函数返回时内存并未以任何方式进行初始化。

    3>调用realloc函数可以改变一块已经动态分配的内存的大小。增加内存块大小有时有可能采取的方法是把原来内存块上的所有数据复制到一个新的、更大的内存块上。当一个动态分配的内存块不再使用时,应该调用free函数把它归还给可用内存池,内存释放后便不能再被访问。

    3.如果请求的内存分配失败,malloc、malloc和readlloc函数返回的将是一个NULL指针。

    4.错误的访问分配内存之外的区域所引起的后果类似越界访问一个数组,但这个错误还能破坏可用内存池,导致程序失败。

    5.如果一个指针不是从早先的malloc、calloc或realloc函数返回的,它是不能作为参数传递给free函数的。


    展开全文
  • C语言指针的内存分配和Java中的引用

    千次阅读 2017-03-06 17:27:32
    2、处理指针相关问题的万能措施—-内存分配图  3、C语言的指针是如何过渡到Java中的引用的 最近一段时间一直在学习C语言的指针,也算是颇有心得吧,虽然从网上看了几篇关于指针的博文,但是感觉都不符合自己的...

    核心内容: 
    1、C语言指针的核心知识点 
    2、处理指针相关问题的万能措施—-内存分配图 
    3、C语言的指针是如何过渡到Java中的引用的


    最近一段时间一直在学习C语言的指针,也算是颇有心得吧,虽然从网上看了几篇关于指针的博文,但是感觉都不符合自己的口味,于是决定好好写一篇关于指针的文章。 
    C语言指针的核心知识点: 
    1、指针就是地址,地址就是内存单元的编号,范围是0到4G-1,指针的本质就是一个操作受限的非负整数,而指针变量就是存放内存单元编号(即地址、指针)的变量。 
    2、凡是动态分配的内存,都是没有名字的,而是将其地址赋给一个指针变量,用指针变量去代表这个事物。 
    3、一个指针变量,无论其指向的变量占多少个字节,其本身只占用4个字节的内存空间,因为内存单元的编号是32位。32/8=4 
    4、字节是存储数据的基本单元,一个字节占8位,而一个字节的编号占32位。 
    5、变量分为两种类型:普通类型变量和指针类型变量,其中普通类型变量用来存放真实的数据,而指针类型变量用来存放变量的地址。其中指针类型变量包括(Java中): 
    ①所有类定义的变量:如 Student student = new Student(“zhang” , 25),其中的student 
    ②所有接口定义的变量:如 List list = new ArrayList(),其中的list 
    ③数组的名字:如int a[] = {1,2,3,8,9}中的a。 
    6、静态内存是在栈中进行分配的,是由系统自动分配、自动释放的,静态内存是程序员无法控制的;动态内存是在堆中进行分配的,是由程序员手动分配,手动释放的,凡是动态分配的内存必须通过free的方式才能够进行释放,当然这里指的是C语言;在Java当中,动态分配的内存是由内存回收机制来回收的,不用程序员来进行手动回收,保证了程序的安全性,但是在Java当中,由于虚拟机要一直跟踪每一块内存空间的使用情况,所以往往会从造成CPU的使用率过大。 
    好的,如果你想学会C语言中的指针,上面的这些内容是你必须要理解的,首先我们先理解一下究竟什么是指针,在理解究竟什么是指针之前,我们必须要知道数据在内存中究竟是如何来进行存储的,先放一张图: 
    这里写图片描述 
    这里通过一个小例子来说明数据在内存中是如何来进行存储的: 
    这里写图片描述 
    当我们在Visiual C++6.0软件对这个程序进行编译运行的时候,Visiual C++6.0这个软件首先请求操作系统为我们的变量i分配一块内存空间,随后操作系统会在内存中寻找一块空闲的区域分配给我们的程序,随后Visiual C++6.0这个软件会将变量i和我们的这块内存空间关联起来,今后对变量i的操作就是对我们内存空间的操作。具体实现过程如下: 
    这里写图片描述 
    现在我们对内存存储的这一块区域进行放大: 
    这里写图片描述 
    操作系统会给我们的变量分配一块内存空间,但是这块内存空间究竟在内存中的什么位置呢?这块内存空间在内存空间中的编号到底是多少呢?现在让我么在程序中输出一下:

    # include <stdio.h>
    
    int main()
    {
        int i = 10;
    
        printf("编号1的数值是:%#X\n",&i);
    
        return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行结果: 
    这里写图片描述 
    现在我们用图画在描述一下: 
    这里写图片描述 
    在上面这张图中:这里写图片描述(即编号1)就是我们所说的指针,即地址,也就是说:指针实际上就是内存单元的编号,一个编号为32位,每一个内存单元都会占有一个内部单元的编号(即地址)记载其在内存条中的位置,因此通过指针我们可以直接对硬件进行操作。 
    其实,程序归根结底就是对内存的操作,我们对一块内存空间进行操作总共含有两种方式: 
    ①直接通过变量名的方式对这块内存空间进行操作。(直接访问) 
    ②通过获取内存空间的地址对这块内存空间进行操作。(间接访问) 
    这里写图片描述 
    其中,第一种方式是我们经常使用的,但是第二种方式会让我们有一种直接接触到硬件的感觉,示例程序:

    # include <stdio.h>
    
    int main()
    {
        int i = 10;
    
        printf("编号1的数值是:%#X\n",&i);
    
        int * p = &i; //指针变量p保存了变量i的地址:18FF44
    
        *p = 100; //以18FF44为地址的那块内存空间的内容设置为100
    
        printf("变量i的内容是:%d\n",i);//无论是直接访问还是以地址的间接访问,本质上都是对同一块内存空间的访问
    
        return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    运行结果: 
    这里写图片描述 
    具体效果: 
    这里写图片描述 
    归根接地就是一句话:无论是通过变量名i的直接访问,还是通过地址18FF44的间接访问,本质上都是对同一块内存空间的访问。


    处理指针相关问题的万能措施—-内存分配图 
    很多人在处理指针这块的程序的时候,有的时候总是会感觉到很迷糊,但是就我个人而言,对于指针相关的知识,总是习惯于去画内存分配图去解决问题,而且效果还是非常好的,下面我们就用一个典型的程序:交换内容的程序来说明问题。 
    要求:输入a和b两个整数,按照先大后小的顺序输出a和b。 
    实例程序1:

    # include <stdio.h>
    
    void swap(int ,int );
    
    int main()
    {
        int a,b;
        printf("请从键盘上输入a和b两个数值:\n");
        scanf("%d %d",&a,&b);
    
        if (a < b)
        {
           swap(a,b);   
        }
    
        printf("max=%d \t min=%d \n",a,b);
    
        return 0;
    }
    void swap(int p,int q)
    {
        int tmp; //交换p和q的内容
        tmp = p;
        p = q;
        q = tmp;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    运行结果: 
    这里写图片描述 
    很明显,从运行结果上来看,并没有达到我们的预期效果,下面我们用内存分配图来查找一下原因: 
    这里写图片描述 
    所以上面程序的解法是错误的。 
    实例程序2:

    # include <stdio.h>
    
    void swap(int *,int *);
    
    int main()
    {
        int a,b;
        printf("请从键盘上输入a和b两个数值:\n");
        scanf("%d %d",&a,&b);
    
        if (a < b)
        {
           swap(&a,&b); 
        }
    
        printf("max=%d \t min=%d \n",a,b);
    
        return 0;
    }
    void swap(int *p,int *q)
    {
        int tmp;
        tmp = *p;
        *p = *q;
        *q = tmp;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    运行结果: 
    这里写图片描述 
    内存分配图: 
    这里写图片描述 
    通过上面的图解我们可以发现,指针变量p和q分别定位到了变量a和变量b的内存空间,间接的交换了a和b内存空间的内容。


    C语言的指针是如何过渡到Java中的引用的 
    在谈到这个问题的时候,我认为应该从两个方面进行说起:动态内存分配和如何传递发送内容。 
    动态内存份分配的问题: 
    实例程序1:

    # include <stdio.h>
    # include <malloc.h>
    # include <string.h>
    
    struct Student
    {
       char name[100];
       int age;
       float score;
    };
    
    int main()
    {
        Student * student = (Student *)malloc(sizeof(Student));
        strcpy(student->name,"zhangming");
        student->age = 25;
        student->score = 88.8f;
    
        printf("name is %s\n",student->name); //student->age在编译底层会变为(*student).name
        printf("age is %d\n",student->age);
        printf("score is %f\n",student->score);
    
    
        return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    运行结果: 
    这里写图片描述 
    内存实例图示: 
    这里写图片描述 
    对于上面的这个程序,Java语言是这么封装的:

    class Student 
    {
       String name;
       int age;
       float score;
    }
    public class App1 
    {
        public static void main(String[] args)
        {
            //Student * student = (Student *)malloc(sizeof(Student));
            Student student = new Student(); //new相当于C语言中的malloc
            student.name = "zhangsan";
            student.age = 25;
            student.score = 88.8f;
    
            System.out.println("name is:"+student.name);
            System.out.println("age is:"+student.age);
            System.out.println("score is:"+student.score);
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    下面我们通过函数传递数据:传递指针变量(本质传递的是地址)

    # include <stdio.h>
    # include <malloc.h>
    # include <string.h>
    
    struct Student
    {
       char name[100];
       int age;
       float score;
    };
    
    void changeData(Student * stu)
    {
        strcpy(stu->name,"lisi");
        stu->age = 24;
        stu->score = 98.8f;
    }
    int main()
    {
        Student * student = (Student *)malloc(sizeof(Student));
        strcpy(student->name,"zhangming");
        student->age = 25;
        student->score = 88.8f;
    
        printf("name is %s\n",student->name); //student->age在编译底层会变为(*student).name
        printf("age is %d\n",student->age);
        printf("score is %f\n",student->score);
    
        changeData(student);//传递的是地址,速度快并且节省内存空间!
    
        printf("name is %s\n",student->name); //student->age在编译底层会变为(*student).name
        printf("age is %d\n",student->age);
        printf("score is %f\n",student->score);
    
        return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    运行结果: 
    这里写图片描述 
    Java封装的效果:

    class Student 
    {
       String name;
       int age;
       float score;
    }
    public class App1 
    {
        public static void main(String[] args)
        {
            //Student * student = (Student *)malloc(sizeof(Student));
            Student student = new Student(); //new相当于C语言中的malloc
            student.name = "zhangsan";
            student.age = 25;
            student.score = 88.8f;
    
            System.out.println("name is:"+student.name);
            System.out.println("age is:"+student.age);
            System.out.println("score is:"+student.score);
    
            changeData(student); //student本质上是一个指针变量
    
            System.out.println("name is:"+student.name);
            System.out.println("age is:"+student.age);
            System.out.println("score is:"+student.score);
        }
        public static void changeData(Student stu) //stu指向同一块内存空间
        {
            stu.name = "lisi";
            stu.age = 24;
            stu.score = 98.8f;
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    运行结果:

    name is:zhangsan
    age is:25
    score is:88.8
    name is:lisi
    age is:24
    score is:98.8
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6


    总结:在Java当中,虽然已经没有了指针,但是底层编译运行过程中本质上就是指针,Java中的引用本质上就是C语言中的指针变量,无论是C语言还是Java语言,都有一个共同的特点:凡是动态分配的内存都是没有名字的,而是用一个指针变量保存这块内存空间的地址,用这个指针变量去代表这块内存空间。 

    展开全文
  • Java内存区域划分和内存分配策略

    千次阅读 多人点赞 2020-05-15 12:32:46
    Java内存区域划分和内存分配策略 如果不知道,类的静态变量存储在那? 方法的局部变量存储在那? 赶快收藏 Java内存区域主要可以分为共享内存,堆、方法区和线程私有内存,虚拟机栈、本地方法栈和程序计数器。如下...

    Java内存区域划分和内存分配策略

    如果不知道,类的静态变量存储在那? 方法的局部变量存储在那? 赶快收藏

    Java内存区域主要可以分为共享内存,方法区和线程私有内存,虚拟机栈、本地方法栈和程序计数器。如下图所示,本文将详细讲述各个区域,同时也会讲述创建对象过程,内存分配策略, 和对象访问定位原理。觉得写得好的,可以点个收藏,绝对不亏。

    Java内存区域

    程序计数器

    程序计数器,可以看作程序当前线程所执行的字节码行号指示器。字节码解释器工作时就是通过改变计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理都需要依赖计数器完成。线程执行Java方法时,记录其正在执行的虚拟机字节码指令地址,线程执行Native方法时,计数器记录为空。程序计数器时唯一在Java虚拟机规范中没有规定任何OutOfMemoryError情况区域。

    理论可知,线程是通过轮流获取CPU执行时间以实现多线程的并发。为了暂停的线程下一次获得CPU执行时间,能正常运行,每一个线程内部都需要维护一个程序计数器,用来记住暂停线程暂停的位置。

    注意:光理论是不够的,在此送大家一套2020最新Java架构实战教程+大厂面试宝典,点击此处 进来获取 一起交流进步哦!

    Java虚拟机栈

    Java虚拟机栈同程序计数器一样,也是线程私有的,虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈、动态链接和方法出入口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

    本地方法栈

    与虚拟机栈相似。虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。

    Java堆

    所有线程共享的一块内存区域。Java虚拟机所管理的内存中最大的一块,因为该内存区域的唯一目的就是存放对象实例。几乎所有的对象实例都在这里分配内存,同时堆也是垃圾收集器管理的主要区域。因此很多时候被称为"GC堆"

    方法区

    和堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、和编译器编译后的代码(也就是存储字节码文件.class)等数据

    方法区中有一个运行时常量池,编译后期生成的各种字面量和符号引用,存放在字节码文件中的常量池中。当类加载进入方法区时,就会把该常量池中的内容放入方法区中的运行时常量池。此外也可以在程序运行期间,将新的常量放入运行时常量池,比如String.intern()方法,该方法先从运行时常量池中查找是否有该值,如果有,则返回该值的引用,否则将该值加入运行时常量池。

    实例详讲

    class Demo1_Car{
        public static void main(String[] args) {
            Car c1 = new Car();
            //调用属性并赋值
            c1.color = "red";
            c1.num = 8;
            //调用行为
            c1.run();
            Car c2 = new Car();
            c2.color = "black";
            c2.num = 4;
            c2.run();
        }
    }
    Class Car{
        String color;
        int num;
        public void run() {
        	System.out.println(color + ".." + num);
    	}
    }
    

    • 首先运行程序,Demo1_car.java就会变为Demo1_car.classDemo1_car.class加入方法区,检查是否字节码文件常量池中是否有常量值,如果有,那么就加入运行时常量池。
    • 遇到main方法,创建一个栈帧,入虚拟机栈,然后开始运行main方法中的程序。
    • Car c1 = new Car(), 第一次遇到Car这个类,所以将Car.java编译为Car.class文件,然后加入方法区.然后new Car(),在堆中创建一块区域,用于存放创建出来的实例对象,地址为0X001.其中有两个属性值colornum。默认值是null和 0
    • 然后通过c1这个引用变量去设置colornum的值,调用run方法,然后会创建一个栈帧,用来存储run方法中的局部变量等。run 方法中就打印了一句话,结束之后,该栈帧出虚拟机栈。又只剩下main方法这个栈帧。
    • 接着又创建了一个Car对象,所以又在堆中开辟了一块内存,之后就是跟之前的步骤一样了。

    创建对象过程

    虚拟机在遇到一条new指令时,会首先检查这个指令的参数是否可以在方法区中定位到一个类的符号引用,并且检查这个符号引用所代表的类是否已经被加载,解析和初始化过。如果没有,则必须先执行类加载过程.

    类加载完之后,需要为对象分配内存,有两种分配内存的方法

    • 指针碰撞法(要求堆内存规整)

      Java堆中空闲内存和已使用内存分别存放在堆的两边,中间存放一个指针作为分界点的指示器,在为对象分配内存时只需要将指针向空闲区域移动创建对象所需要的内存大小即可。

    • 空闲列表法

      如果堆内存中已使用内存区域和空闲区域相互交错,此时虚拟机需要维护一个列表,记录哪些内存块是可用的,在分配时从列表中找到一块足够大的内存区域划分给对象实例并更新列表上的记录。

    多线程情况下,线程同时分配内存可能会造成冲突,比如使用指针碰撞法,线程A正在分配内存,还没有改变指针指向,线程B,又同时使用原来的指针进行内存分配。防止冲突有两种方法

    • CAS操作:虚拟机采用CAS操作,加上失败重试的方式保证内存分配的原子性
    • 本地线程分配缓冲(TLAB):预先为线程分配一部分堆内存空间(线程私有,所以不存在同步问题)用于对象实例的内存分配。只有当TLAB用完,需要分配新的TLAB时,才需要进行同步操作。

    内存分配完之后,虚拟机需要将分配到的内存空间均初始化为零值(不包括对象头)。在虚拟机中,执行完new指令后会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来

    对象在内存中的布局

    对象在内存中的布局如下图所示,分为对象头、实例数据、对齐填充
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传在这里插入图片描述

    • 对象头(可以参考Java锁升级)

      mark Word, 用于存储对象自身的运行时数据,如哈希码、GC分代年龄以及锁状态标志等。类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

    • 实例数据

      对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。

    • 对齐填充

      并非必然存在,仅仅起着占位符的作用。

    对象的访问定位

    Java程序需要通过栈上的reference数据来操作堆上的具体对象。共有两种策略进行对象的访问定位

    • 句柄访问

      Java堆中划分出一块内存来作为句柄池,reference中存储的是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息,需要两次寻址。

    • 直接指针访问

      Java堆中对象的布局中需要考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。

    使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中实例数据指针,而reference本身不需要修改。

    问题

    只需要记住一件事,就是Java对象的内存分配均是在堆中进行的。所以对象都存储在堆中

    但是有人可能会怀疑方法的临时变量不是存储在虚拟机栈中吗?这里我要解释一下,虚拟机栈维护了一个局部变量表,表中存储的是对象的引用,而真正存储对象的地方在堆,如果局部变量都在堆里分配,那么虚拟机栈早爆满了

    同样类的静态变量,有人又会怀疑在方法区中存储。其实不是的,方法区只存储引用,具体对象是存储在堆中的,具体实现可以发现,类静态对象是与class对象一起分配的内存。

    注意:光理论是不够的,在此送大家一套2020最新Java架构实战教程+大厂面试宝典,点击此处 进来获取 一起交流进步哦!

    参考

    深入理解java虚拟机

    展开全文
  • Java内存区域划分、内存分配原理

    万次阅读 多人点赞 2014-11-16 16:20:30
    总结自《深入理解Java虚拟机》之内存区域划分。
  • 在计算机系统中,运行的应用程序的数据都是保存在内存中的,不同类型的数据,保存的内存区域不同。 一、内存分区 栈区(stack) 由编译器自动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程...
  • iOS开发探究--内存分配分区

    千次阅读 2016-06-05 14:35:53
    ios内存分配分区1.RAM和ROM RAM:运行内存,不能掉电储存. ROM:储存性内存,可以掉电储存,例如:内存卡,flash 由于RAM类型不具备掉电储存能力(即一掉电数据就会丢失),所以app程序一般存放于 ROM中,RAM的访问速度要远...
  • 这篇写得很好,以下...变量就是计算机内存中的一小块内存单元,储存在这一小块内存单元中的值叫做变量的值。 例如 int a = 1; a 就是变量名, 1 就是变量值。 而当一个变量指向一个对象时,这个变量就叫做引用...
  • 2.对象引用:对象引用,是一个变量,这个变量中保存了另一个java对象的内存地址,对象引用储存在栈内存中。 3.new运算符:new运算符的作用是创建对象,在JVM中开辟新的内存空间。 4.JVM的存储机制 二.实例分析 ...
  • Java堆是被所有线程共享的一块内存区域,主要用于存放对象实例,Java虚拟机规范中有这样一段描述:所有的对象实例和数据都要在堆上进行分配。 为对象分配内存方式:指针碰撞法(内存完整时)、空闲列表法  
  • JVM之内存区域分配

    千次阅读 2016-03-20 17:06:21
    JVM的内存模型,对象的分配
  • 一个操作系统必须要有完善的内存管理系统(页/段式的管理),相应的jvm全称java虚拟机应该也有类似的一种管理内存的方式,这种方式是建立在真实的操作系统内存管理方式之上的,他把内存分配成了不同的区域,形成了...
  • 对象的释放是由垃圾回收机制决定和执行的,这样极大的简化CG(垃圾处理装置)的负担,当然同时也为程序员带来便利(例如c语言需要手动的去处理已经不在使用的对象,如果遗忘内存就会被越占越多)。 可以分为2大类:堆内存与...
  • 内存动态分区分配与回收

    千次阅读 2019-12-11 20:53:24
    要求有录入界面,动态初始化内存使用情况,动态录入进程对内存分配与回收请求. <4>.有算法选择界面,动态选择分区分配算法. <5>.有输出界面,能够查看内存分配与回收的结果. (2)初始化 我是创了一个进.....
  • 下面试着举例说明:JVM内存分区:如上图所示,JVM主要分为以上几块:程序计数器,本地方法栈,虚拟机栈,堆和方法区。稍微粗糙一些得分法是JVM分为栈和堆,栈包括虚拟机栈,本地方法栈,程序计数器,堆分为堆和方法...
  • 图解Golang的内存分配

    万次阅读 2019-06-09 20:11:07
    一般程序的内存分配 在讲Golang的内存分配之前,让我们先来看看一般程序的内存分布情况: 以上是程序内存的逻辑分类情况。 我们再来看看一般程序的内存的真实(真实逻辑)图: Go的内存分配核心思想 Go是内置运行时的...
  • 我们可以通过vs来判断一个变量在栈上以及堆上的地址,其中值类型仅仅在栈上分配内存,我们先看值类型的地址怎么查?如下: 程序卡在断点-》在局部变量所在的区域(vs下方)右击添加监视-》在监视的名称一栏输入"&变量...
  • 内存分配

    千次阅读 2013-01-24 10:58:25
    到目前为止,我们已经使用过kmalloc...除非被阻塞,否则这个函数可运行得很快不对所获取的内存空间清零,也就是分配给它的区域仍然保持着原有的数据它分配区域在物理内存中也是连续的 flags参数 记住kmalloc的
  • Java 虚拟机内存分配机制

    千次阅读 2017-09-15 15:34:45
    内存区域划分 对于大多数的程序员来说,Java 内存比较流行的说法便是堆和栈,这其实是非常粗略的一种划分,这种划分的“堆”对应内存模型的 Java 堆,“栈”是指虚拟机栈,然而 Java 内存模型远比这更复杂,想深入...
  • 1.类型(没有string)八种基本数据类型分别是:int、short、float、double、long、boolean、byte、...引用数据类型默认为null基本类型值:保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。引用类...
  • 堆栈以及内存分配

    千次阅读 2015-05-24 16:36:18
    1.在IOS中系统是怎么分配变量的?  1. 内存是所有应用程序共享的 2.内存分配是由系统来分配的...5.如果变量使用结束后,需要释放内存,OC中当一个变量的引用计数为0,就说明没有任何变量使用该空间,系统就直接收回 6.
  • java中内存分配策略及堆和栈的比较  1 内存分配策略   按照编译原理的观点,程序运行时的内存分配策略,分别是三种:a静态的,b栈式的,和c堆式的.  a.静态存储分配是指在编译时就能确定每个数据目标在运行时刻的...
  • Java内存管理-内存分配与回收

    千次阅读 2017-12-29 17:48:00
    Java内存管理-内存分配与回收
  • 众所周知,我们在编程的时候经常会在函数中声明局部变量(包括普通类型的变量、指针、引用等等)。...看似很简单的问题,通过仔细的分析,我们就能够更好的理解c++中内存分配和释放的问题。 好,废话不多说,我们进
  • java内存分配

    千次阅读 2015-04-23 23:19:09
    JVM包括编译器是C语言实现的,而一个由C/C++编译的程序占用的内存分为以下几个部分: 1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 2、堆区...
  • 再探Java内存分配

    千次阅读 多人点赞 2017-09-01 20:56:54
    这两天有个同事抓耳挠腮地纠结:Java到底是值传递...一提到内存分配,我想不少人的脑海里都会浮现一句话:引用放在栈里,对象放在堆里,栈指向堆。嗯哼,这句话听上去没有错;但是我们继续追问一下:这个栈是什么栈?
  • C#内存分配

    千次阅读 2012-09-30 09:35:07
    问题2:哪些操作会 创建对象和分配内存? 问题3:内存的分配机制?   1.CLR管理内存的三块区域 注:内存——堆栈 堆(托管堆) 线程的堆栈:用于分配值类型的实例-有操作系统管理分配释放内存。 GC...
  • java的内存分配机制

    千次阅读 2014-10-20 23:46:54
    当在一个代码块中定义一个变量的时候,java就在栈中为其分配内存,当超过作用域的时候内存自动释放。  对内存用来存放new创建的对象和数组。在堆中分配的内存,由java虚拟机的垃圾回收机器管理。java的堆是运行时...
  •  Java虚拟机在执行Java的过程中会把管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,而有的区域则依赖线程的启动和结束而创建和销毁。 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 172,769
精华内容 69,107
关键字:

引用需要分配内存区域