Manual Memory: allocate & free
Every language here is garbage-collector-free, so a heap allocation is a resource you own and must hand back. The task is the same in all seven: put a single int on the heap, set it to 42, print it, then release it. Watch who frees the memory - by hand (free/Free/FREE), via a destructor (C++ RAII), or scheduled with defer (Zig, Hare, Odin) - and notice that every malloc needs exactly one matching free: no more (double-free), no fewer (leak).
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(sizeof *p); /* one int on the heap */
if (p == NULL) { /* malloc can fail: always check */
perror("malloc");
return 1;
}
*p = 42;
printf("%d\n", *p); /* prints 42 */
free(p); /* YOU must free what YOU malloc */
p = NULL; /* avoid a dangling pointer */
return 0;
}Idiomatic C11: malloc(sizeof *p) sizes the allocation from the pointer (no type repeated), and the caller owns the block - one free(p) matches the one malloc. Forgetting it leaks; calling it twice is undefined behavior, so nulling p after free defuses the dangling pointer.
#include <iostream>
#include <memory>
int main() {
// make_unique allocates an int and constructs it with 42.
auto p = std::make_unique<int>(42);
std::cout << *p << '\n'; // prints 42
// No delete: ~unique_ptr frees the int automatically at scope exit
// (RAII), even if an exception unwinds the stack.
return 0;
}Modern C++ replaces new/delete with RAII: std::make_unique<int>(42) owns the heap int, and the unique_ptr destructor calls delete exactly once when p leaves scope. Ownership is unique and move-only - there is no manual free to forget and no double-free.
// Top-level code runs in TempleOS - no main() needed.
I64 *p = MAlloc(sizeof(I64)); // grab 8 bytes from this task's heap
*p = 42;
Print("%d\n", *p); // prints 42
Free(p); // return it; Free(NULL) is allowed
HolyC allocates from the per-task data heap with MAlloc and reclaims with Free (calling Free(NULL) is a safe no-op). There is no memory protection in ring-0 TempleOS, so a double-free or stray write can corrupt the whole system - though when the task dies its heap is reclaimed wholesale.
const std = @import("std");
pub fn main() !void {
// The allocator is explicit - no hidden allocations in Zig.
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit(); // reports leaks on exit
const allocator = gpa.allocator();
const p = try allocator.create(i32); // *i32, may fail -> error union
defer allocator.destroy(p); // runs at scope exit, no matter what
p.* = 42;
std.debug.print("{d}\n", .{p.*}); // prints 42
}Zig threads the Allocator explicitly: allocator.create(i32) returns !*i32 (allocation can fail, hence try), and defer allocator.destroy(p) pairs the free right next to the alloc so it always runs at scope exit. The GeneralPurposeAllocator even reports leaks on deinit - the matching free is enforced, not hoped for.
use fmt;
export fn main() void = {
// alloc returns *int; it aborts the program if the heap is exhausted.
let p: *int = alloc(42);
defer free(p); // release at scope exit
fmt::println(*p)!; // prints 42
};Hare's alloc(42) heap-allocates an int initialized to 42 and yields *int (it aborts rather than returning null on failure). defer free(p) keeps the matching free beside the allocation and runs it when main returns - manual memory with a Zig/Go-style cleanup hook and no GC.
package main
import "core:fmt"
main :: proc() {
// new(int) uses the implicit context.allocator; returns ^int.
p := new(int)
defer free(p) // freed via the same context allocator at scope end
p^ = 42
fmt.println(p^) // prints 42
}Odin's new(int) allocates through the implicit context.allocator (no allocator argument needed) and returns ^int; free(p) returns it to that same allocator, scheduled with defer. Swap context.allocator for an arena and you could drop the per-object free entirely - there is no GC and no hidden destructor.
\ Heap allocation via the optional ALLOCATE/FREE word set.
: DEMO ( -- )
1 CELLS ALLOCATE THROW ( -- addr ) \ room for one cell; THROW on error
42 OVER ! ( addr -- addr ) \ store 42 at addr
DUP @ . ( addr -- addr ) \ fetch and print -> 42
FREE THROW ( addr -- ) \ hand the block back
;
DEMOForth has no GC and no types: ALLOCATE (the optional dynamic-memory word set) requests 1 CELLS of heap and pushes address then an ior status - THROW turns a nonzero status into an error. You write with !, read with @, and pair every ALLOCATE with one FREE by hand. (Static data instead would use CREATE 1 CELLS ALLOT, which is never freed - it lives in the dictionary for the program's life.)