← Code Compare

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.

Show: CC++HolyCZigHareOdinForth
C
#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.

C++
#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.

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

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

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

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