Pointers & Addresses
A pointer is a value that holds the address of another value, letting you read and write that storage indirectly. This topic takes the address of a variable, mutates it through the pointer, and prints both the value and (conceptually) its address - the foundation every systems language builds allocation, aliasing, and ownership on top of. Watch where the storage lives: most of these examples point at a stack variable (no cleanup), and the heap variants make allocation and free explicit so the lifetime is obvious.
#include <stdio.h>
int main(void) {
int value = 41; /* lives on the stack */
int *ptr = &value; /* &value: address-of; ptr now aliases value */
*ptr += 1; /* dereference and write through the pointer */
printf("value = %d\n", value); /* 42 */
printf("address = %p\n", (void *)ptr); /* %p wants a void* */
return 0;
}& takes an address, * dereferences it; ptr is a raw alias to a stack variable, so there is nothing to free. Print pointers with %p and cast to void * to avoid undefined behavior.
#include <iostream>
#include <memory>
int main() {
// Heap allocation owned by a unique_ptr: freed automatically (RAII).
auto value = std::make_unique<int>(41);
int* raw = value.get(); // non-owning observer; do NOT delete it
*raw += 1; // mutate the owned int through the raw pointer
std::cout << "value = " << *value << '\n'; // 42
std::cout << "address = " << static_cast<void*>(raw) << '\n';
// unique_ptr's destructor frees the int here; no manual delete.
}std::make_unique<int> owns the heap allocation and frees it on scope exit (RAII), so no delete is needed. get() returns a non-owning raw pointer used purely to observe and mutate; ownership stays with the unique_ptr.
I64 *ptr = MAlloc(sizeof(I64)); // heap cell; MAlloc does NOT zero it
*ptr = 41; // so assign a known value first
*ptr += 1; // dereference + modify
Print("value = %d\n", *ptr); // 42
Print("address = 0x%X\n", ptr); // raw address
Free(ptr); // hand the cell back
HolyC runs top-level code with no main. MAlloc/Free are the heap pair (per-task heap); MAlloc returns uninitialized memory (use CAlloc if you need a zeroed cell), so we write before reading. * dereferences just like C, and the address is printed with %X since HolyC has no %p.
const std = @import("std");
pub fn main() void {
var value: i32 = 41; // stack variable
const ptr: *i32 = &value; // *i32 is a single-item pointer; &value takes the address
ptr.* += 1; // .* dereferences; write through the pointer
std.debug.print("value = {d}\n", .{value}); // 42
std.debug.print("address = {*}\n", .{ptr}); // {*} prints the address
}Zig pointers are explicit types (*i32 = single-item, non-null); & takes an address and ptr.* dereferences. This points at a stack variable, so there is no allocator and nothing to free - Zig never hides allocations.
use fmt;
export fn main() void = {
let value: int = 41; // stack variable
let ptr: *int = &value; // *int pointer; &value takes the address
*ptr += 1; // dereference and modify
fmt::printfln("value = {}", value)!; // 42
fmt::printfln("address = {}", ptr: uintptr)!; // cast the pointer to a printable integer
};Hare pointers use *T with & to take an address and *ptr to dereference. Pointing at a stack let needs no alloc/free; printing an address means casting the pointer to uintptr. The trailing ! propagates any I/O error.
package main
import "core:fmt"
main :: proc() {
value := 41 // stack variable
ptr := &value // ptr: ^int, a single pointer; &value takes the address
ptr^ += 1 // ^ postfix dereferences; write through the pointer
fmt.println("value =", value) // 42
fmt.printf("address = %p\n", ptr) // %p formats the pointer
}Odin spells pointers ^int, takes addresses with &, and dereferences with the postfix ^. Here value is on the stack, so the implicit context.allocator is untouched - no new/free. (Heap form: p := new(int); defer free(p).)
\ Forth has no named variables: VARIABLE reserves one cell and pushes
\ its address when named -- the variable IS its pointer.
VARIABLE value ( -- ) \ reserve 1 cell of storage
41 value ! ( store 41 at the cell whose addr `value` pushes )
value @ 1+ value ! ( fetch, add 1, store back -- modify through the addr )
." value = " value @ . CR ( @ dereferences: -> 42 )
." address = " value U. CR ( `value` already IS the address; print it )Forth's VARIABLE is a pointer: naming it pushes its address, ! stores through that address, and @ dereferences. There is no &/* - the cell is statically reserved in the dictionary, so nothing is allocated or freed at runtime.