Memoria
Cómo gestiona Antril la memoria - el recolector que elige cada proyecto, destructores, regiones, referencias y el verificador de préstamos, código unsafe y punteros, y búferes fijos.
En esta página
Los valores (números, bool, tipos value, enums) viven donde se declaran y se copian. Los objetos de clases, las cadenas y los arreglos viven en el heap, y las variables guardan referencias a ellos. La forma en que se devuelve la memoria del heap se elige por proyecto, con gc: en el chunk BUILD de su archivo .queen:
chunk BUILD:
gc: refcount
Modos de memoria
gc: |
Cómo se liberan los objetos | Cuándo se ejecutan los destructores |
|---|---|---|
default (o tracing) |
Un recolector de basura por trazado encuentra los objetos inalcanzables y los libera. | Después de que el recolector encuentra que el objeto es inalcanzable; no necesariamente antes de que termine el programa. |
refcount |
Cada objeto cuenta sus referencias y se libera cuando la cuenta llega a cero. Los ciclos no se liberan: rómpelos tú (por ejemplo, asignando null a un campo). |
En cuanto desaparece la última referencia. |
arena |
Los objetos se liberan juntos cuando se cierra la region que los creó. |
Cuando se cierra la región. |
manual |
Liberas cada objeto con delete. |
Con delete. |
custom |
Reservado para un recolector que escribe el proyecto, indicado con gc.collector. En esta versión System.Core no tiene una interfaz para él, y el proyecto se compila con el recolector por trazado. |
Si nada indica lo contrario, el modo es default. larva config gc <mode> cambia el valor predeterminado en tu máquina (consulta El comando larva).
El mismo código fuente compila en todos los modos. Lo que cambia es cuándo se liberan los objetos y, por lo tanto, cuándo se ejecutan sus destructores. Puedes usar desde tu proyecto bibliotecas compiladas para otro modo; las cadenas y los bytes pasan de una a otra a través de System.Exchange.
Destructores
Una clase puede declarar destroy(). Se ejecuta una sola vez, cuando el objeto deja de existir; el destructor de una clase derivada se ejecuta antes que el de su base.
import System;
into namespace Mem
{
public class Resource
{
str name;
public:
new(str name)
{
this.name = name;
Console.WriteLine("open " + name);
}
destroy()
{
Console.WriteLine("close " + name);
}
}
Use()
{
Resource r = new Resource("a");
Console.WriteLine("using a");
}
Main()
{
Use();
Resource b = new Resource("b");
b = new Resource("c");
Console.WriteLine("end");
}
}
En un proyecto con gc: refcount, cada objeto se destruye en cuanto nada lo referencia:
larva run
open a
using a
close a
open b
open c
close b
end
close c
Memoria manual
Con gc: manual, delete obj; ejecuta el destructor y libera el objeto. Liberar memoria a mano no es seguro, así que delete va en código unsafe:
import System;
into namespace Mem
{
public class Resource
{
public:
destroy() { Console.WriteLine("closed"); }
}
Main()
{
Resource r = new Resource();
Console.WriteLine("using it");
unsafe { delete r; }
Console.WriteLine("end");
}
}
larva run
using it
closed
end
delete a través de una clase base ejecuta el destructor de la clase real del objeto.
Regiones
Con gc: arena, un bloque region { … } libera todos los objetos creados dentro de él cuando el bloque termina, sin importar cómo termine (también con break o return):
import System;
into namespace Mem
{
public class Resource
{
str name;
public:
new(str name) { this.name = name; }
destroy() { Console.WriteLine("close " + name); }
}
Main()
{
region
{
Resource a = new Resource("a");
Resource b = new Resource("b");
Console.WriteLine("leaving the region");
}
Console.WriteLine("end");
}
}
larva run
leaving the region
close b
close a
end
Un objeto creado en una región no puede sobrevivirla. Devolverlo, por ejemplo, es un error al compilar el programa:
larva build
larva: error[RegionEscape]: `return` holds a value from region r0 and is still used after region_exit(r0)
--> Mem.$Module.Escape bb1[4]
Los objetos creados fuera de toda región viven hasta que termina el programa.
El recolector
System.GC se comunica con el recolector por trazado: GC.Collect() pide una recolección, GC.CollectionCount() indica cuántas se han ejecutado y GC.ObjectCount() cuántos objetos están vivos. En los otros modos, los objetos se liberan sin recolector.
Referencias y el verificador de préstamos
&T es una referencia compartida (solo lectura, varias a la vez) y &mut T una exclusiva (lectura y escritura, una a la vez). &x y &mut x toman prestada una variable, un parámetro, un campo o un elemento. Los miembros, los métodos y la indexación funcionan a través de una referencia.
public value Counter
{
public:
i32 n;
}
Bump(&mut Counter c)
{
c.n = c.n + 1;
}
Read(&Counter c) : i32 => c.n;
Main()
{
Counter c;
Bump(&mut c);
Bump(&mut c);
Console.WriteLine(Read(&c)); // 2
&mut Counter r = &mut c;
r.n = 7;
Console.WriteLine(c.n); // 7
}
Las referencias las comprueba el verificador de préstamos. Una referencia vive hasta su último uso; mientras vive un préstamo exclusivo, nada más puede usar ni tomar prestado el mismo lugar. Una referencia no se puede devolver desde la función a cuya variable local apunta, ni puede ser un campo de una clase ni un argumento de tipo.
El verificador tiene tres configuraciones, que se eligen con borrowcheck: en BUILD, con --borrowcheck en larva build y larva run, o para tu máquina con larva config borrowcheck:
| Configuración | Qué ocurre |
|---|---|
off |
El valor predeterminado. Las referencias no están disponibles: &T y &x son errores que te piden activar el verificador. |
warn |
Los errores de préstamo se informan como advertencias y el programa se compila. |
enforce |
Los errores de préstamo detienen la compilación. |
Un ejemplo de lo que rechaza enforce:
Counter c;
&mut Counter r = &mut c;
c.n = 1; // c is borrowed by r, which is used on the next line
r.n = 7;
larva build --check --borrowcheck enforce app.ant
app.ant(15,3): error[ConflictingBorrow]: cannot assign to `c.0` while it is borrowed (mutable borrow of `c` on line 14)
Código unsafe y punteros
Los punteros solo se permiten en código unsafe: una función unsafe, un tipo unsafe o un bloque unsafe { … }.
| Sintaxis | Qué hace |
|---|---|
T*, void* |
Un puntero a un número, un enum, un value o un puntero (no a un objeto). |
&x |
La dirección de x. |
*p, p[i], p->field |
Lee y escribe a través del puntero. |
p + n, p - q, p < q |
Aritmética en elementos (en bytes para void*), distancia, comparación. |
func*<A> : R |
Un puntero a una función de C. |
import System;
unsafe Fill(i32* p, i32 count, i32 value)
{
loop (i32 i = 0..count)
p[i] = value;
}
Main()
{
i32[] data = new i32[4];
unsafe
{
Fill(&data[0], data.Length, 9);
i32* second = &data[0] + 1;
*second = 1;
}
Console.WriteLine(data[0] + data[1] + data[2] + data[3]);
}
larva run
28
Los punteros son la forma en que Antril llama a bibliotecas de C; consulta Bindings nativos.
Un proyecto puede prohibir el código unsafe con unsafe: deny en BUILD: entonces cualquier función, tipo o bloque unsafe, y cualquier tipo puntero, en los archivos del proyecto es un error. Las bibliotecas que usa no se ven afectadas.
Búferes fijos
fixed T[N] name; guarda un arreglo de N elementos dentro de un value o una clase, sin una asignación aparte. sizeof(T) da el tamaño de un tipo en bytes.
Una clase final también puede guardar un arreglo de longitud variable dentro de cada objeto, como su último campo: fixed T[] name;. Cada constructor indica cuántos elementos tiene el objeto con : fixed(n). El objeto y sus elementos se asignan juntos, en una sola pieza. El campo se lee y se escribe por índice y tiene un Length, pero no es un valor de arreglo: no se puede asignar, pasar ni devolver.
import System;
into namespace Low
{
public value Header
{
public:
u32 Tag;
fixed u8[8] Name;
}
public final class Samples
{
fixed i32[] items;
public:
new(i32 count) : fixed(count)
{
loop (i32 i = 0..count)
items[i] = i * i;
}
i32 Count { get => items.Length; }
At(i32 i) : i32 => items[i];
}
Main()
{
Header h;
h.Tag = 7;
h.Name[0] = 65;
Console.WriteLine(h.Tag + h.Name[0]);
Console.WriteLine(sizeof(Header));
Samples s = new Samples(5);
Console.WriteLine(s.Count + " " + s.At(4));
}
}
larva run
72
12
5 16
Arreglos
T[] es un arreglo cuya longitud queda fija al crearlo. Los índices se comprueban: un índice fuera de rango detiene el programa (larva panic: index 3 out of bounds for length 1, código de salida 101).
i32[] zeros = new i32[10]; // ten zeros
str?[] names = new str?[3]; // three nulls
i32[] some = [1, 2, 3]; // a literal
i32[,] grid = new i32[3, 4]; // rectangular, up to 8 dimensions
grid[2, 3] = 23;
i32[][] rows = [[1, 2], [3]]; // an array of arrays
Un arreglo nuevo empieza en ceros, así que su tipo de elemento necesita un valor cero. Para una clase o str, usa un tipo de elemento anulable (new Shape?[n]), un literal o un List<T>.
Siguiente: Código asíncrono y reactivo.
Editar esta página en GitHub Reportar un problema