A pointer is a value used to refer to an object or function. For beginner C, start with pointers to ordinary variables: identify the object, obtain its address, and access it only while that object exists. You do not need manual allocation to understand this core idea.
Connect a pointer to a real object
int number = 10 creates an integer object. int *pointer = &number creates a pointer initialized with that object’s address. The ampersand takes an address. In an expression, *pointer dereferences the pointer to access the object it designates.
The star has different roles in a declaration and an expression. In int *pointer it helps describe the type; in *pointer = 20 it selects the pointed-to object for assignment. After the assignment, number is 20. There is still one integer object, accessed through two expressions.
int number = 10;
int *pointer = &number;
*pointer = 20;
printf("Value: %d\n", number);
printf("Address: %p\n", (void *)pointer);A type is not a validity certificate
An int * should refer to an appropriately aligned, live int object when you dereference it. Declaring a pointer without initializing it does not create that object. Nor does assigning an arbitrary numeric address make the memory valid. Use an existing object’s address or a properly checked allocation.
A null pointer deliberately refers to no object. Initialize an optional pointer to NULL and check it before use, but remember that non-null is not sufficient proof of validity. A pointer can be non-null and still point to expired or freed storage. Lifetime is a separate requirement from its numeric appearance.
Use pointers to update caller-owned values
A swap function receives two copied addresses, reads the first object into a temporary, and exchanges the contents. Calling swap(&a, &b) gives it access to a and b. A version that accepted two ordinary int parameters would change only its local copies.
This explains scanf’s &number argument: the function needs a place to store a converted value. Passing number instead provides the wrong kind of argument. Read warnings about format types carefully; they often expose a pointer mistake before it becomes a runtime failure.
void swap(int *left, int *right) {
int temporary = *left;
*left = *right;
*right = temporary;
}Arrays and pointers are related, not identical
In many expressions, an array converts to a pointer to its first element. Passing int values[] to a function therefore does not deliver a self-describing collection. Pass a count as well. At the array declaration site, sizeof values measures the array; inside a pointer parameter, it measures the pointer.
Pointer arithmetic advances in units of the pointed-to type. pointer + 1 moves to the next int when pointer points into an int array. Such arithmetic must stay within that array or its one-past position. You may use a one-past pointer as a boundary, but you may not dereference it.
Object lifetime controls safe access
An ordinary local variable lives until execution leaves its block. Returning its address does not extend that lifetime. The address might look unchanged afterwards, but using it to access the expired object is invalid. Return a value or write into storage owned by the caller instead.
Allocated storage has a different lifetime: malloc requests a block and returns NULL if it cannot satisfy the request. A successful block must eventually be released with free when no longer needed. After free, neither the original pointer nor any copied alias may be used to access that storage. Setting one pointer to NULL does not repair the other aliases.
Read const declarations from the object outward
const int *p allows changing p to point elsewhere, but disallows modifying the integer through p. int *const p makes p itself fixed after initialization while allowing modification of its pointed-to integer. These are different promises, and both can appear together.
Use const on read-only pointer parameters to communicate intent and let the compiler reject accidental writes through that interface. It does not ensure that another alias cannot modify the same object. It also does not make an invalid address safe or turn writable storage into permanent storage.
Debug by naming the object
When a pointer expression looks confusing, ask: which object should this designate, where was that object created, what is its type, and when does its lifetime end? Then ask whether the index or offset is within bounds. These questions are more reliable than staring at printed addresses.
Start with the swap example and predict each assignment before running it. Next try the structure example, where record->score means access score through a pointer to the record. Avoid using crashes as your only test: WebAssembly detects some out-of-bounds memory operations, but not every invalid C pointer operation becomes an immediate trap.