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.

Última actualización: 9 min de lectura Editar en GitHub

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:

.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.

AntrilRun
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:

Terminal
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:

AntrilRun
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");
	}
}
Terminal
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):

AntrilRun
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");
	}
}
Terminal
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:

Terminal
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.

Antril
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:

Antril
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;
Terminal
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.
AntrilRun
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]);
}
Terminal
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.

AntrilRun
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));
	}
}
Terminal
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).

Antril
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.

¿Te resultó útil esta página?

No se guarda ninguna dirección; tu nombre de usuario sí, si iniciaste sesión.

Esc