精华内容
下载资源
问答
  • 讨论之前,先看如下代码: 1 type treeNode struct { 2 value int 3 left, right *treeNode 4 } 5 6 func createNode(value int) *treeNode { 7 return &treeNode{value:value} 8 } 9 ...

    在讨论之前,先看如下代码:

     1 type treeNode struct {
     2     value int
     3     left, right *treeNode
     4 }
     5 
     6 func createNode(value int) *treeNode {
     7     return &treeNode{value:value}
     8 }
     9 
    10 func main() {
    11     root := createNode(10)
    12     fmt.Println(root)
    13 }

    上面这段代码createNode函数返回了一个局部变量的地址给main函数中的root,但是fmt.Println正常打印出来了新建的node的内容。这要是在C++中这么写,是个很典型的错误:返回局部变量的地址,该地址的内容在函数退出后会被自动释放,因为是在栈上的。

    那么go语言的局部变量到底是在栈上还是堆上呢?go语言编译器会做逃逸分析(escape analysis),分析局部变量的作用域是否逃出函数的作用域,要是没有,那么就放在栈上;要是变量的作用域超出了函数的作用域,那么就自动放在堆上。所以不用担心会不会memory leak,因为go语言有强大的垃圾回收机制。这样可以释放程序员的内存使用限制,让程序员关注程序逻辑本身。

     

    对于new出来的局部变量,也不是一定就放在堆上,而是根据其是否超出了函数作用域来判断是否放在堆上还是栈上。这点和C语言很不一样。

    转载于:https://www.cnblogs.com/howo/p/9417927.html

    展开全文
  • 局部变量内存分配可能在上也可能在堆上 堆和的简单说明: 1.(操作系统):由操作系统自动分配释放 2.堆(操作系统): 一般由程序员分配释放,例如在c/c++中,在golang,java,python有自动的垃圾回收机制 ...
  • final修饰的变量就是常量?final修饰局部变量在栈还是堆还是常量池中?

    概念

    什么是常量?

    对于这个问题,可能很多人都可以脱口而出 : 用final修饰的变量是常量 ,或者是在编译时期定义好的字符串。

    但是这种说法是不严谨的,因为准确来说 : 常量是用final修饰的成员变量!常量在类编译时期载入类的常量池中。

    即final修饰的成员变量(实例变量)和静态变量(静态变量也就是用static修饰的成员变量),那么用final修饰的局部变量(方法内)我们也可以称之为不可变变量。(存储在栈中)

    常量池

    Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。

    • 静态常量池 : *.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。(编译时期)

    • 运行时常量池 : jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。(运行时期)

    补充 : 运行时常量池中的常量,基本来源于各个class文件中的常量池。(即每个class文件都有对应的常量池)

    常量池的好处

    常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。

    1. 节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。

    2. 节省运行时间:比较字符串时,比equals()快。对于两个引用变量,只用判断引用是否相等,也就可以判断实际值是否相等

    双等号==的含义
    基本数据类型之间应用双等号,比较的是他们的数值。
    复合数据类型(类)之间应用双等号,比较的是他们在内存中的存放地址。(引用地址)

    String hello="helloMoto";   
    String hello2="helloMoto";  
    

    例如我们定义hello和hello2,并且字符串常量池中没有存在”helloMoto”这个字符串常量。
    那么首先会在字符串常量池中创建”helloMoto”字符串对象,hello指向字符串常量池中”helloMoto”字符串对象。
    第一行代码,hello2首先会去常量池中寻找是否有”helloMoto”,发现已经存在,就直接指向该字符串常量池中”helloMoto”字符串对象。

    Class类文件中的常量池

    • 魔数: 每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。很多文件存储标准中都使用魔数来表示身份识别。使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑,因为文件扩展名可以随意的改动。Class文件的魔数有很浪漫的气息,值为0x CAFEBABE这也是java是咖啡图标和商标名的原因之一。
    • 版本号: 紧接着4个魔数字节后面存储的是Class文件的版本号:第5和6个字节是次版本号,第7和第8个字节是主版本号。
    • 常量池: 接着主次版本号之后的是常量池入口,常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一。(Class类文件中的常量池在类未加载到内存中可以称为静态常量池) 。入口处用2个字节标识常量池常量数量。

    常量池

    常量池主要用于存放两大类常量: 字面量和符号引用量

    字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值(成员变量)等。

    符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

    • 类和接口的全限定名
    • 字段名称和描述符
    • 方法名称和描述符

    运行时常量池

    • 在Class类文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用如不过不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。 这部分内容将在类加载后进入方法区的运行时常量池中存放。
    • 运行时常量池相对于CLass文件常量池(静态常量池)的另外一个重要特征是 具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。

    包装类常量池(对象池)

    java中基本类型的包装类的大部分都实现了常量池技术,即Byte、Short、Integer、Long、Character、Boolean、Float、Double

    Integer i1 = 127;
    Integer i2 = 127;
    System.out.println(i1==i2);//true
    
    
    Integer i3 = 128;
    Integer i4 = 128;
    System.out.println(i3==i4);//false
    

    对于上面2段代码不同结果我们可以追溯Integer源码

    // Integer
    public static Integer valueOf(int i) {
        if (i >= -128 && i <= 127)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
    

    可以看到如果值位于[-128,127]区域中,会使用IntegerCache类缓存数据,类似于字符串常量池。

    所以如果赋的值超出这个区域, 便会创建一个新的Integer对象。(好处是平时如果频繁的使用Integer,并且数值在[-128,127]中,便不会重复创建新的Integer对象)

    但是DoubleFloat这两个基本数据类型的包装类就没有对应常量池(对象池)的实现。

    // Double 
    public static Double valueOf(double d) {
        return new Double(d);
    }
    

    Java中装箱和拆箱

    基本数据类型 包装类
    int(4字节) Integer
    byte(1字节) Byte
    short(2字节) Short
    long(8字节)) Long
    float(4字节) Float
    double(8字节) Double
    char(2字节) Character
    boolean(1字节) Boolean

    赋值时

    • 装箱: 自动将基本数据类型转换为包装器类型
    //  如果要生成一个数值为10的Integer对象,只需要这样:
    Integer i = 10;
    // 这个过程中会自动根据数值创建对应的 Integer对象,这就是装箱。
    
    • 拆箱: 自动将包装器类型转换为基本数据类型
    Integer i = 10;  // 装箱
    int n = i;       // 拆箱
    

    方法调用时

    public class Test2 {
        public static void main(String[] args) {
            // int值 5 转换成对应的Integer对象(装箱)
            int result = print(5);
        }
     
        // 接收Integer对象作为参数
        private static int print(Integer a) {
            System.out.println("a==" + a);
            // 返回int 类型,Integer自动拆箱转为int类型。
            return a;
        }
    }
     
    //a==5
    

    方法运算时

    public class Test2 {
        public static void main(String[] args) {
            Integer sum = 0;
            for (int i = 1000; i < 5000; i++) {
            	// 自动拆箱为int类型才能运算
            	// 运算结果再自动装箱为Integer类型
                sum += i;
            }
        }
    }
    

    上面的代码sum+=i可以看成sum = sum + i,但是+这个操作符不适用于Integer对象,首先sum进行自动拆箱操作,进行数值相加操作,最后发生自动装箱操作转换成Integer对象。其内部变化如下

    int result = sum.intValue() + i;
    Integer sum = new Integer(result);
    

    由于我们这里声明的sum为Integer类型,在上面的循环中会创建将近4000个无用的Integer对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。因此在我们编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题。

    展开全文
  •  C++中,局部函数里面,new一个内存块,地址是在堆中,而其他的变量基本在中。中的变量内存随着局部函数的结束而被自动销毁,堆的则不会,需要自己delete销毁。  举个常见的错误: int* Func1() { int p...
  • go语言局部变量分配在栈还是堆?

    千次阅读 2018-08-26 13:50:42
    对于动态new出来的局部变量,go语言编译器也会根据是否有逃逸行为来决定是分配在堆还是栈,而不是直接分配在堆中。 $ cat main.go package main func foo(m0 * int) (*int) { var m1 * int = new(int); var ...

          转载地址:https://www.jianshu.com/p/4e3478e9d252

        

    一个问题引发的思考?
    如下go语言代码

    package main
    
    func foo(m0 int) (*int) {
      var m1 int = 11;
      return &m1
    }
    
    func main() {
      m := foo(100)
      println(*m)
    }
    

    编译运行:

    $ go build main.go && ./main 
    11
    

    竟然没有出现任何编译错误。
    熟悉C/C++语言的同学,立马就能看出这个程序是存在"问题"的,即函数foo把局部变量m1的地址给返回了,当函数foo返回的时候,m1的地址是会消亡的,这个代码有很严重的问题,一般的C/C++编译器都会给出警告错误:

    $ cat main.c 
    #include <stdio.h>
    
    int * foo(int m0) {
      int m1 = 11;
      return &m1;
    }
    
    int main() {
      int * m = foo(100);
      printf("%d\n", *m);
    }
    $
    $
    $ gcc main.c 
    main.c:8:11: warning: address of stack memory associated with local variable 'm1' returned [-Wreturn-stack-address]
      return &m1;
              ^~
    1 warning generated.
    

    如上C/C++编译器明确给出了警告,foo把一个局部变量的地址返回了;反而高大上的go没有给出任何警告,难道是go编译器识别不出这个问题吗?
    答案不是的,参考go FAQ里面的一段话:

    How do I know whether a variable is allocated on the heap or the stack?

    From a correctness standpoint, you don't need to know. Each variable in Go exists as long as there are references to it. The storage location chosen by the implementation is irrelevant to the semantics of the language.

    The storage location does have an effect on writing efficient programs. When possible, the Go compilers will allocate variables that are local to a function in that function's stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.

    In the current compilers, if a variable has its address taken, that variable is a candidate for allocation on the heap. However, a basic escape analysis recognizes some cases when such variables will not live past the return from the function and can reside on the stack.

    意思是说go语言编译器会自动决定把一个变量放在栈还是放在堆,编译器会做逃逸分析(escape analysis),当发现变量的作用域没有跑出函数范围,就可以在栈上,反之则必须分配在堆。
    go语言声称这样可以释放程序员关于内存的使用限制,更多的让程序员关注于程序功能逻辑本身。

    个人觉得这是扯淡,属于自作主张的小聪明,非常不喜欢这个设计。还是C/C++的逻辑非常清楚:简单逻辑是,声明的局部变量分配在栈,通过动态申请(malloc, new)的内存在堆里。

    不爽归不爽,既然go语言这么实践了,我们还是来举证一下这个例子:

    $ cat main.go
    package main
    
    func foo(m0 int) (*int) {
      var m1 int = 11;
      var m2 int = 12; 
      var m3 int = 13; 
      var m4 int = 14; 
      var m5 int = 15; 
    
      println(&m0, &m1, &m2, &m3, &m4, &m5)
      println(&m0, &m1, &m2, &m3, &m4, &m5)
      println(&m0, &m1, &m2, &m3, &m4, &m5)
      println(&m0, &m1, &m2, &m3, &m4, &m5)
      println(&m0, &m1, &m2, &m3, &m4, &m5)
    
      return &m3
    }
    
    func main() {
      m := foo(100)
      println(*m)
    }
    $
    $ go tool compile -m main.go |more
    main.go:16:10: &m3 escapes to heap
    main.go:6:7: moved to heap: m3
    main.go:10:11: foo &m0 does not escape
    main.go:10:16: foo &m1 does not escape
    ...
    $
    $ go build main.go && ./main 
    0xc420041f58 0xc420041f38 0xc420041f30 0xc420012068 0xc420041f28 0xc420041f20
    0xc420041f58 0xc420041f38 0xc420041f30 0xc420012068 0xc420041f28 0xc420041f20
    0xc420041f58 0xc420041f38 0xc420041f30 0xc420012068 0xc420041f28 0xc420041f20
    0xc420041f58 0xc420041f38 0xc420041f30 0xc420012068 0xc420041f28 0xc420041f20
    0xc420041f58 0xc420041f38 0xc420041f30 0xc420012068 0xc420041f28 0xc420041f20
    13
    

    可以看出同样定义的局部变量m0, m1, .... m5,他们的的地址是有差异的:m0作为传入参数是分配在栈上,m1, m2, m4, m5也分配在栈上的连续地址,而m3的地址却在堆上,因为编译器分析出m3变量有逃逸行为。

    我们再看一下编译器生成的汇编码:

    var m1 int = 11;
      0x003e 00062 (/.../src/main/main.go:4) MOVQ  $11, "".m1+40(SP)
    var m2 int = 12;
      0x0047 00071 (/.../src/main/main.go:5) MOVQ  $12, "".m2+32(SP)
    var m3 int = 13;
      0x0050 00080 (/.../src/main/main.go:5) LEAQ  type.int(SB), AX
      0x0057 00087 (/.../src/main/main.go:6) MOVQ  AX, (SP)
      0x005b 00091 (/.../src/main/main.go:6) PCDATA  $0, $0
      0x005b 00091 (/.../src/main/main.go:6) CALL  runtime.newobject(SB)
      0x0060 00096 (/.../src/main/main.go:6) MOVQ  8(SP), AX
      0x0065 00101 (/.../src/main/main.go:6) MOVQ  AX, "".&m3+288(SP)
      0x006d 00109 (/.../src/main/main.go:6) MOVQ  $13, (AX)
    var m4 int = 14;
      0x0074 00116 (/.../src/main/main.go:7) MOVQ  $14, "".m4+24(SP)
    var m5 int = 15;
      0x007d 00125 (/.../src/main/main.go:8) MOVQ  $15, "".m5+16(SP)
    

    看到对m3的处理调用了库函数runtime.newobject(...),所以把m3存放在堆中。

    看来确实是go语言会根据局部变量有没有发生逃逸行为来自动决定一个局部变量是分配在栈,还是分配在堆。

    对于动态new出来的局部变量,go语言编译器也会根据是否有逃逸行为来决定是分配在堆还是栈,而不是直接分配在堆中。

    $ cat main.go
    package main
    
    func foo(m0 * int) (*int) {
      var m1 * int = new(int);
      var m2 * int = new(int);
      var m3 * int = new(int);
      var m4 * int = new(int);
      var m5 * int = new(int);
    
      println(m0, m1, m2, m3, m4, m5)
      println(m0, m1, m2, m3, m4, m5)
      println(m0, m1, m2, m3, m4, m5)
      println(m0, m1, m2, m3, m4, m5)
      println(m0, m1, m2, m3, m4, m5)
      println(m0, m1, m2, m3, m4, m5)
      println(m0, m1, m2, m3, m4, m5)
      println(m0, m1, m2, m3, m4, m5)
      println(m0, m1, m2, m3, m4, m5)
      println(m0, m1, m2, m3, m4, m5)
      println(m0, m1, m2, m3, m4, m5)
      println(m0, m1, m2, m3, m4, m5)
      println(m0, m1, m2, m3, m4, m5)
    
      return m3
    }
    
    func main() {
      n := 100
      m := foo(&n)
      println(*m)
    }
    $
    $
    $ go tool compile -m main.go |more
    main.go:6:21: new(int) escapes to heap
    main.go:3:22: foo m0 does not escape
    main.go:4:21: foo new(int) does not escape
    main.go:5:21: foo new(int) does not escape
    main.go:7:21: foo new(int) does not escape
    main.go:8:21: foo new(int) does not escape
    main.go:29:12: main &n does not escape
    $
    $ go build main.go && ./main 
    0xc420041f60 0xc420041f28 0xc420041f20 0xc420012068 0xc420041f18 0xc420041f30
    0xc420041f60 0xc420041f28 0xc420041f20 0xc420012068 0xc420041f18 0xc420041f30
    0xc420041f60 0xc420041f28 0xc420041f20 0xc420012068 0xc420041f18 0xc420041f30
    0xc420041f60 0xc420041f28 0xc420041f20 0xc420012068 0xc420041f18 0xc420041f30
    0xc420041f60 0xc420041f28 0xc420041f20 0xc420012068 0xc420041f18 0xc420041f30
    0xc420041f60 0xc420041f28 0xc420041f20 0xc420012068 0xc420041f18 0xc420041f30
    0xc420041f60 0xc420041f28 0xc420041f20 0xc420012068 0xc420041f18 0xc420041f30
    0xc420041f60 0xc420041f28 0xc420041f20 0xc420012068 0xc420041f18 0xc420041f30
    0xc420041f60 0xc420041f28 0xc420041f20 0xc420012068 0xc420041f18 0xc420041f30
    0xc420041f60 0xc420041f28 0xc420041f20 0xc420012068 0xc420041f18 0xc420041f30
    0xc420041f60 0xc420041f28 0xc420041f20 0xc420012068 0xc420041f18 0xc420041f30
    0xc420041f60 0xc420041f28 0xc420041f20 0xc420012068 0xc420041f18 0xc420041f30
    0xc420041f60 0xc420041f28 0xc420041f20 0xc420012068 0xc420041f18 0xc420041f30
    0
    

    和前面例子一样,m0作为参数分配在栈中,而 m1, m2, m4, m5也是分配在栈中,尽管他们都是通过new动态分配出来的,只有m3分配在堆中,原因是m3有逃逸行为。

    结论就是一个函数内局部变量,不管是不是动态new出来的,它会被分配在堆还是栈,是由编译器做逃逸分析之后做出的决定。


     

    展开全文
  • go局部变量的存储空间是堆还是栈? 编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定的。 var global *int func f() { var x int ...
        

    go局部变量的存储空间是堆还是栈?

    编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定的。

    var global *int
    
    func f() {
        var x int
        x = 1
        global = &x
    }
    
    func g() {
        y := new(int)
        *y = 1
    }

    f函数里的x变量必须在堆上分配,因为它在函数退出后依然可以通过包一级的global变量找到,虽然它是在函数内部定义的;用Go语言的术语说,这个x局部变量从函数f中逃逸了。相反,当g函数返回时,变量y将是不可达的,也就是说可以马上被回收的。因此,y并没有从函数g中逃逸,编译器可以选择在栈上分配*y的存储空间(译注:也可以选择在堆上分配,然后由Go语言的GC回收这个变量的内存空间),虽然这里用的是new方式。其实在任何时候,你并不需为了编写正确的代码而要考虑变量的逃逸行为,要记住的是,逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响。

    Go语言的自动垃圾收集器对编写正确的代码是一个巨大的帮助,但也并不是说你完全不用考虑内存了。你虽然不需要显式地分配和释放内存,但是要编写高效的程序你依然需要了解变量的生命周期。例如,如果将指向短生命周期对象的指针保存到具有长生命周期的对象中,特别是保存到全局变量时,会阻止对短生命周期对象的垃圾回收(从而可能影响程序的性能)。

    展开全文
  • 先看这两个代码: 这两段代码只是调用的...fmt.println函数使局部变量的作用域超出了函数的作用域,所以局部变量在堆上。而println是内联函数,并没有使局部变量的作用域逃出函数的作用域,所以是在上。 ...
  • 1、区(stack)由编译器自动分配释放 ,存放函数的参数值,局部变量的值等,内存的分配是连续的,类似于平时我们所说的,如果还不清楚,那么就把它想成数组,它的内存分配是连续分配的,即,所分配的内存是一...
  • c++的成员变量在堆还是栈

    千次阅读 2018-05-11 15:51:33
    即:如果对象是函数内的非静态局部变量,则对象,对象的成员变量保存在栈区。如果对象是全局变量,则对象,对象的成员变量保存静态区。如果对象是函数内的静态局部变量,则对象,对象的成员变量保存静态区。如果...
  • 第一种: 方法中声明的变量,即该变量是局部变量,每当程序调用方法时,系统都会为该方法建立一个方法,其所在方法中声明的变量就放在方法中,当方法结束系统会释放方法,其对应该方法中声明的变量随着的...
  • c++变量在堆还是栈

    千次阅读 2012-08-03 16:50:09
     C++中,局部函数里面,new一个内存块,地址是在堆中,而其他的变量基本在中。中的变量内存随着局部函数的结束而被自动销毁,堆的则不会,需要自己delete销毁。  举个常见的错误: int* Func1() { int p[2]...
  • 成员变量随着对象的建立而建立,随着对象的消失而消失,存在于对象所在的内存中局部变量:1.局部变量定义局部范围内:如方法,函数,语句中,只作用域有效2.局部变量没有默认初始化值3.局部变量存在于内存中...
  • #include int qj1 = 1; int qj2 = 2; int main() { printf("%p %p %s\n",&qj1,&qj2,(&qj2-&qj1>0)?"增加":"减少");...全局变量(上)递增存储,局部变量中)递减存储 。感觉就像从中间向两边发散
  • 这里有解释
  • new创建对象,对象保存在堆还是栈? –>堆内存是用来存放由new创建的对象和数组,即动态申请的内存都存放在堆内存 –>栈内存是用来存放在函数中定义的一些基本类型的变量和对象的引用变量 例子:局部变量存放...

空空如也

空空如也

1 2 3 4 5 ... 17
收藏数 336
精华内容 134
关键字:

局部变量在堆还是栈