2012-06-02 8 views
96

Soy nuevo en Go y estoy experimentando una cierta disonancia congitiva entre la programación basada en pila estilo C donde las variables automáticas viven en la pila y la memoria asignada vive en el montón y la programación basada en la pila al estilo de Python, donde lo único que vive en la pila son referencias/punteros a objetos en el montón.Asignación de estructuras de pila y de montón en Go, y cómo se relacionan con la recolección de basura

Por lo que yo puedo decir, las dos funciones siguientes dan el mismo resultado:

func myFunction() (*MyStructType, error) { 
    var chunk *MyStructType = new(HeaderChunk) 

    ... 

    return chunk, nil 
} 


func myFunction() (*MyStructType, error) { 
    var chunk MyStructType 

    ... 

    return &chunk, nil 
} 

es decir, asignar una nueva estructura y devolverlo.

Si hubiera escrito eso en C, el primero habría puesto un objeto en el montón y el segundo lo hubiera puesto en la pila. El primero devolvería un puntero al montón, el segundo devolvería un puntero a la pila, que se habría evaporado cuando la función regresara, lo que sería una cosa mala.

Si lo hubiera escrito en Python (o en muchos otros idiomas modernos, excepto en C#), el ejemplo 2 no hubiera sido posible.

Supongo que Go garbage recopila ambos valores, por lo que ambos formularios están bien.

Para citar:

Tenga en cuenta que, a diferencia de C, es perfectamente correcto para devolver la dirección de una variable local ; el almacenamiento asociado con la variable sobrevive después de que la función regrese. De hecho, tomar la dirección de un compuesto literal asigna una nueva instancia cada vez que se evalúa, por lo que puede combinar estas dos últimas líneas.

http://golang.org/doc/effective_go.html#functions

pero plantea un par de preguntas.

1 - En el ejemplo 1, la estructura se declara en el montón. ¿Qué pasa con el ejemplo 2? ¿Eso está declarado en la pila de la misma manera que sería en C o también está en el montón?

2 - Si se declara el ejemplo 2 en la pila, ¿cómo queda disponible después de que la función vuelve?

3 - Si el ejemplo 2 realmente se declara en el montón, ¿cómo es que las estructuras se pasan por valor en lugar de por referencia? ¿Cuál es el punto de los indicadores en este caso?

Respuesta

111

Vale la pena señalar que las palabras "apilar" y "montón" no aparecen en ninguna parte de la especificación del idioma. Su pregunta está redactada con "... está declarado en la pila" y "... declarado en el montón", pero tenga en cuenta que la sintaxis de la declaración Go no dice nada sobre la pila o el montón.

Depende técnicamente de la respuesta a todas sus preguntas. En realidad, por supuesto, hay una pila (por goroutine!) Y un montón, y algunas cosas van en la pila y otras en el montón. En algunos casos, el compilador sigue reglas rígidas (como "new siempre asigna en el montón") y en otros el compilador hace "análisis de escape" para decidir si un objeto puede vivir en la pila o si debe asignarse en el montón.

En su ejemplo 2, el análisis de escape mostraría el puntero a la estructura de escape y, por lo tanto, el compilador tendría que asignar la estructura. Creo que la implementación actual de Go sigue una regla rígida en este caso, que es que si la dirección se toma de cualquier parte de una estructura, la estructura continúa en el montón.

Para la pregunta 3, corremos el riesgo de confundirnos con la terminología. Todo en Go pasa por valor, no hay pase por referencia. Aquí está devolviendo un valor de puntero. ¿Cuál es el punto de los punteros? Considere la siguiente modificación de su ejemplo:

type MyStructType struct{} 

func myFunction1() (*MyStructType, error) { 
    var chunk *MyStructType = new(MyStructType) 
    // ... 
    return chunk, nil 
} 

func myFunction2() (MyStructType, error) { 
    var chunk MyStructType 
    // ... 
    return chunk, nil 
} 

type bigStruct struct { 
    lots [1e6]float64 
} 

func myFunction3() (bigStruct, error) { 
    var chunk bigStruct 
    // ... 
    return chunk, nil 
} 

he modificado MyFunction2 para devolver la estructura en lugar de la dirección de la estructura. Comparar la salida de montaje de MyFunction1 y MyFunction2 ahora,

--- prog list "myFunction1" --- 
0000 (s.go:5) TEXT myFunction1+0(SB),$16-24 
0001 (s.go:6) MOVQ $type."".MyStructType+0(SB),(SP) 
0002 (s.go:6) CALL ,runtime.new+0(SB) 
0003 (s.go:6) MOVQ 8(SP),AX 
0004 (s.go:8) MOVQ AX,.noname+0(FP) 
0005 (s.go:8) MOVQ $0,.noname+8(FP) 
0006 (s.go:8) MOVQ $0,.noname+16(FP) 
0007 (s.go:8) RET  , 

--- prog list "myFunction2" --- 
0008 (s.go:11) TEXT myFunction2+0(SB),$0-16 
0009 (s.go:12) LEAQ chunk+0(SP),DI 
0010 (s.go:12) MOVQ $0,AX 
0011 (s.go:14) LEAQ .noname+0(FP),BX 
0012 (s.go:14) LEAQ chunk+0(SP),BX 
0013 (s.go:14) MOVQ $0,.noname+0(FP) 
0014 (s.go:14) MOVQ $0,.noname+8(FP) 
0015 (s.go:14) RET  , 

No se preocupe que la producción MyFunction1 aquí es diferente que en (excelente) La respuesta de peterSO. Obviamente estamos ejecutando compiladores diferentes. De lo contrario, vea que modifiqué myFunction2 para devolver myStructType en lugar de * myStructType. La llamada a runtime.new se ha ido, lo que en algunos casos sería algo bueno. Un momento, sin embargo, aquí está myFunction3,

--- prog list "myFunction3" --- 
0016 (s.go:21) TEXT myFunction3+0(SB),$8000000-8000016 
0017 (s.go:22) LEAQ chunk+-8000000(SP),DI 
0018 (s.go:22) MOVQ $0,AX 
0019 (s.go:22) MOVQ $1000000,CX 
0020 (s.go:22) REP  , 
0021 (s.go:22) STOSQ , 
0022 (s.go:24) LEAQ chunk+-8000000(SP),SI 
0023 (s.go:24) LEAQ .noname+0(FP),DI 
0024 (s.go:24) MOVQ $1000000,CX 
0025 (s.go:24) REP  , 
0026 (s.go:24) MOVSQ , 
0027 (s.go:24) MOVQ $0,.noname+8000000(FP) 
0028 (s.go:24) MOVQ $0,.noname+8000008(FP) 
0029 (s.go:24) RET  , 

todavía no llamar a runtime.new, y sí que realmente funciona para devolver un objeto de 8 MB por valor. Funciona, pero normalmente no querrías. El punto de un puntero aquí sería evitar empujar objetos de 8MB.

+7

Excelente gracias. Realmente no me preguntaba "¿para qué sirve punteros?", Era más como "¿qué sentido tiene el puntero cuando los valores parecen comportarse como punteros?", Y de todos modos ese caso es irrelevante. – Joe

+7

Se agradecería una breve explicación del montaje. – ElefEnt

39
type MyStructType struct{} 

func myFunction1() (*MyStructType, error) { 
    var chunk *MyStructType = new(MyStructType) 
    // ... 
    return chunk, nil 
} 

func myFunction2() (*MyStructType, error) { 
    var chunk MyStructType 
    // ... 
    return &chunk, nil 
} 

En ambos casos, las implementaciones actuales de Go sería asignar memoria para un tipo de structMyStructType en un montón y devolver su dirección. Las funciones son equivalentes; la fuente del asm del compilador es la misma.

--- prog list "myFunction1" --- 
0000 (temp.go:9) TEXT myFunction1+0(SB),$8-12 
0001 (temp.go:10) MOVL $type."".MyStructType+0(SB),(SP) 
0002 (temp.go:10) CALL ,runtime.new+0(SB) 
0003 (temp.go:10) MOVL 4(SP),BX 
0004 (temp.go:12) MOVL BX,.noname+0(FP) 
0005 (temp.go:12) MOVL $0,AX 
0006 (temp.go:12) LEAL .noname+4(FP),DI 
0007 (temp.go:12) STOSL , 
0008 (temp.go:12) STOSL , 
0009 (temp.go:12) RET  , 

--- prog list "myFunction2" --- 
0010 (temp.go:15) TEXT myFunction2+0(SB),$8-12 
0011 (temp.go:16) MOVL $type."".MyStructType+0(SB),(SP) 
0012 (temp.go:16) CALL ,runtime.new+0(SB) 
0013 (temp.go:16) MOVL 4(SP),BX 
0014 (temp.go:18) MOVL BX,.noname+0(FP) 
0015 (temp.go:18) MOVL $0,AX 
0016 (temp.go:18) LEAL .noname+4(FP),DI 
0017 (temp.go:18) STOSL , 
0018 (temp.go:18) STOSL , 
0019 (temp.go:18) RET  , 

Calls

En una llamada a la función, el valor de la función y los argumentos se evalúan en el orden habitual. Después de que se evalúan, los parámetros de la llamada se pasan por valor a la función y la función llamada comienza la ejecución .Los parámetros de retorno de la función pasan por el valor de regreso a la función de llamada cuando la función retorna.

Todos los parámetros de función y retorno se pasan por valor. El valor del parámetro de retorno con tipo *MyStructType es una dirección.

+0

Gracias mucho! Upvoted, pero estoy aceptando a Sonia por el poco sobre el análisis de escape. – Joe

+0

peterSo, ¿cómo están usted y @Sonia produciendo ese conjunto? Ambos tienen el mismo formato. No puedo producirlo independientemente de command/flags, habiendo probado objdump, go tool, otool. –

+0

Ah, lo tengo - gcflags. –

10

Según Go's FAQ:

si el compilador no puede probar que la variable no se hace referencia después devuelve la función, el compilador debe asignar la variable de el montón de recolección de basura para evitar colgando errores puntero .

Cuestiones relacionadas