Help improve SovranCode?

Google Analytics can measure anonymous usage after you choose Allow. Essential account and progress features work either way.

SovranCode
C: Foundations for Systems Programming Pointers and Memory Addresses
This device
Course contentsPointers and Memory Addresses · 15 titles

C foundations

What C Is and How C Programs RunYour First C ProgramVariables, Types, and OperatorsInput and Output with printf and scanfConditions and Loops

Program structure and data

Functions, Headers, and ScopeArrays and StringsPointers and Memory AddressesStructures, Enumerations, and UnionsFiles and Command-Line Arguments

Memory, tooling, and practice

Dynamic Memory AllocationPreprocessor Directives and Separate CompilationErrors, Debugging, and Undefined BehaviorBuild Tools and MakefilesProject: Command-Line Inventory Manager
C: Foundations for Systems Programming15 complete lessons

C foundations

What C Is and How C Programs RunYour First C ProgramVariables, Types, and OperatorsInput and Output with printf and scanfConditions and Loops

Program structure and data

Functions, Headers, and ScopeArrays and StringsPointers and Memory AddressesStructures, Enumerations, and UnionsFiles and Command-Line Arguments

Memory, tooling, and practice

Dynamic Memory AllocationPreprocessor Directives and Separate CompilationErrors, Debugging, and Undefined BehaviorBuild Tools and MakefilesProject: Command-Line Inventory Manager
PREVIOUS LESSONArrays and Strings
NEXT LESSONStructures, Enumerations, and Unions
Program structure and data 150 min

Pointers and Memory Addresses

A pointer is a typed value that stores the address of an object. It lets a function reach caller-owned data, lets array traversal describe a current position, and makes memory relationships explicit—but only while the address, lifetime, type, and valid range all agree.

What you will leave with

You will be able to distinguish an object from its address, declare and initialize typed pointers, use address-of and dereference operators, explain pointer-to-const versus const-pointer rules, mutate a caller-owned value deliberately, connect array indexing to pointer arithmetic, print addresses correctly, and reject null, uninitialized, out-of-range, and dangling pointers.

Every safe pointer answers four questions

Where?Which object or array element does this address identify?
What type?How should C interpret the bytes reached through this pointer?
Still alive?Has the pointed-to object's lifetime begun, and has it not yet ended?
Inside range?Does this access remain within the object or array that owns the storage?

An object and its address are different values

int score = 18; creates an int object named score. The expression score reads its value, 18. The expression &score produces the address where that object lives. Because the object is an int, the address has type int *—“pointer to int.”

A real address may look like 0x7ffd..., but its numeric spelling is not the useful contract. Addresses can differ across runs, machines, optimization levels, and operating systems. Reason from identity—“the address of score”—instead of memorizing a particular number.

objectscore

An int object with its own storage and lifetime.

value18

The whole-number value currently stored in that object.

address&score

A pointer value identifying where the object lives.

access*pointer

The object reached by following a valid pointer.

C FUNDAMENTALS RUNNER

Give scanf an address where it may store input

scanf needs &score because it must write into the existing score object rather than receive a copy of its current value.

#include <stdio.h>

int main(void) {
  int score;

  printf("Enter a score: ");
  scanf("%d", &score);
  printf("Stored score: %d\n", score);
  return 0;
}
TERMINAL OUTPUT
Edit the program, predict its output, then run it.

This safe browser runner supports the lesson subset: scalar and fixed-size int/double arrays, character strings, indexed reads and numeric writes, arithmetic, printf/puts, numeric scanf into scalar or bounded array-element addresses, comparisons, bounded if/else, for/while loops, and return.

Try this next: Remove the address-of operator from score, run again, and read the contract error. Restore &score, then try a different input.

Declare the pointed-to type, then initialize the pointer

int score = 18;
int *score_pointer = &score;

printf("value: %d\n", score);
printf("address: %p\n", (void *)&score);
printf("stored address: %p\n", (void *)score_pointer);

The star in int *score_pointer belongs to the declaration: it says the variable stores the address of an int. Initialize a pointer at declaration whenever possible. An uninitialized local pointer has an indeterminate value; following it is not a search for a useful object—it is undefined behavior.

Read pointer declarations from the name outward

In int *score_pointer, score_pointer is a pointer to int. In const int *reading, reading points to an int that this access path must not modify.

Dereference means “the object at this address”

If score_pointer contains &score, then *score_pointer designates the same object as score. In a value expression it reads the object. On the left side of assignment it writes the object.

int score = 18;
int *score_pointer = &score;

printf("before: %d\n", *score_pointer);
*score_pointer = 20;
printf("after: %d\n", score);

The pointer is not the integer 18 and it is not another score object. It stores an address. The dereferenced expression identifies the original object, so the assignment changes what a later read through score observes.

A pointer parameter creates an explicit mutation channel

C passes every argument by value, including pointers. A function receives a copy of the address, but both the caller's pointer expression and the parameter can lead to the same object. That is how a function can update caller-owned data without C having a separate pass-by-reference mechanism.

void add_bonus(int *score, int bonus) {
  if (score == NULL) {
    return;
  }
  *score = *score + bonus;
}

int main(void) {
  int result = 16;
  add_bonus(&result, 2);
  printf("%d\n", result);
  return 0;
}
  1. 01
    The caller takes an address

    &result identifies the caller-owned int.

  2. 02
    The call copies that pointer value

    The parameter score receives its own pointer value, not a new integer object.

  3. 03
    The function validates the address

    The null check proves the pointer represents an object before dereference.

  4. 04
    Dereference reaches shared storage

    *score designates result, so assignment is visible to the caller.

NULL means “no object,” not “safe object”

NULL is a null pointer constant. Use it when a pointer deliberately refers to no object yet or when “not found” is part of an interface. It is valid to assign, compare, or return a null pointer. It is never valid to dereference one.

valid

Points to a live object

The type agrees, the object's lifetime is active, and the intended access remains inside its bounds.

null

Points to no object

Test pointer != NULL before dereference when null is permitted by the function contract.

uninitialized

Contains an indeterminate value

Do not read or dereference it. Initialize it with a valid address or NULL.

dangling

Outlived its object

The old address remains stored, but the referred object's lifetime has ended. Do not use it.

Array expressions connect indexing to pointer arithmetic

In most expressions, an array name converts to a pointer to its first element. For int scores[4], the expression scores usually has the same value as &scores[0]. This is why a function parameter written as int scores[] behaves as int *scores.

Pointer arithmetic advances in elements, not raw bytes. If cursor points to an int, cursor + 1 points to the next int. The identity scores[index] == *(scores + index) explains array indexing; it does not remove the need for a count.

C FUNDAMENTALS RUNNER

Observe the array object that a pointer would reach

This safe runner keeps pointer arithmetic visible in the explanation while you verify that indexed access reads and writes the same array elements.

#include <stdio.h>

int main(void) {
  int scores[4] = {12, 15, 18, 20};

  scores[1] = scores[1] + 2;
  printf("Updated: %d\n", scores[1]);
  printf("Third: %d\n", scores[2]);
  return 0;
}
TERMINAL OUTPUT
Edit the program, predict its output, then run it.

This safe browser runner supports the lesson subset: scalar and fixed-size int/double arrays, character strings, indexed reads and numeric writes, arithmetic, printf/puts, numeric scanf into scalar or bounded array-element addresses, comparisons, bounded if/else, for/while loops, and return.

Try this next: Change index 1 to index 3, predict the updated value, and keep every index between 0 and 3. In a local compiler, rewrite scores[2] as *(scores + 2).

One-past is a boundary, not an element

C permits forming a pointer one position past an array for loop comparison, but dereferencing that pointer is undefined behavior. With four elements, scores + 4 can mark the stopping position; *(scores + 4) cannot read a fifth element.

Place const according to what must not change

pointer to const intconst int *readingreading = &other; /* yes */ *reading = 4; /* no */The pointer may point elsewhere, but this access path cannot modify the int.
const pointer to intint *const reading = &score*reading = 4; /* yes */ reading = &other; /* no */The pointer must keep its initial address, but it may modify the pointed-to int.
const pointer to const intconst int *const reading = &score/* neither the address nor the int may change through reading */Both promises apply to this access path.

An address is valid only during the object's lifetime

A local object normally lives from the execution of its declaration until control leaves its block. Returning &local_value from a function produces a pointer to an object whose lifetime ends as the function returns. The address may still look plausible, but dereferencing it is undefined behavior.

int *broken_result(void) {
  int local_value = 42;
  return &local_value; /* wrong: local_value soon stops existing */
}

Later, dynamic allocation introduces another lifetime boundary: allocated storage remains alive until free, and every retained pointer to it becomes invalid after that call. For now, prefer addresses of caller-owned objects whose lifetime clearly covers the function call.

Print addresses with %p and void *

Use printf("%p\n", (void *)pointer); for diagnostic address output. Do not use %d: an address is not an int, and mismatching a variadic format with its argument type is undefined behavior. Treat the printed address as temporary diagnostic evidence, not stable application data.

Independent lab: update caller-owned readings safely

First run the browser starter to practice supplying three valid element addresses to scanf. Then copy the local-compiler extension below and replace each indexed update with a small pointer function. Keep the count beside the pointer whenever more than one element is accessible.

C FUNDAMENTALS RUNNER

Store three readings through explicit addresses

Each &readings[index] identifies one live element inside the array; the bounded loop then consumes the same three objects.

#include <stdio.h>

int main(void) {
  int readings[3] = {0, 0, 0};
  int total = 0;

  scanf("%d %d %d", &readings[0], &readings[1], &readings[2]);
  for (int index = 0; index < 3; index++) {
    total = total + readings[index];
  }

  printf("Total: %d\n", total);
  return 0;
}
TERMINAL OUTPUT
Edit the program, predict its output, then run it.

This safe browser runner supports the lesson subset: scalar and fixed-size int/double arrays, character strings, indexed reads and numeric writes, arithmetic, printf/puts, numeric scanf into scalar or bounded array-element addresses, comparisons, bounded if/else, for/while loops, and return.

Try this next: Change the three inputs, predict the total, then try an out-of-range fourth address in a local compiler only after increasing the array capacity and loop count together.

void increase_all(int *values, int count, int amount) {
  if (values == NULL || count < 0) {
    return;
  }

  for (int index = 0; index < count; index++) {
    *(values + index) += amount;
  }
}
01Trace one objectDraw separate boxes for score and score_pointer; put the value in one and an arrow in the other.
02Write a swap functionImplement void swap(int *left, int *right), validate both pointers, and exchange the two pointed-to values.
03Traverse with a countImplement sum(const int *values, int count) without modifying the array.
04Test the boundaryUse an address sanitizer locally and confirm that one-past may be compared but never dereferenced.

Lesson review

  • &object produces the object's address; its pointer type follows the object type.
  • *pointer designates the pointed-to object only when the pointer is valid to dereference.
  • C passes a pointer parameter by value, but that copied address can still reach caller-owned storage.
  • A pointer does not carry an array length. Pass the valid element count separately.
  • Pointer arithmetic advances by pointed-to elements and must remain within one array object or its one-past boundary.
  • NULL represents no object and must be checked before dereference when the contract permits it.
  • Uninitialized and dangling pointers do not identify objects you may safely access.
  • Use const int * for read-only element access and %p with void * for diagnostic address output.
KNOWLEDGE CHECK

Follow the address without losing the object

Answer from the pointer contract: know the pointed-to type, prove the object is alive, preserve its valid range, and dereference only a valid address.

01What does &score produce when score is an int variable?
02What does *score_pointer mean when score_pointer points to a valid int?
03Which declaration creates a pointer that initially refers to score?
04Why can a function with an int * parameter change the caller's int?
05What must happen before dereferencing a pointer that may be NULL?
06In most expressions, what does an array name such as scores provide?
07Why does a function that receives int *scores also need a count?
08When scores points to the first array element, what is *(scores + 2)?
09Which pointer becomes dangling?
10How should a pointer value itself be printed for diagnostics?
PREVIOUS LESSONArrays and Strings
NEXT LESSONStructures, Enumerations, and Unions
ON THIS PAGEPointers and Memory AddressesSafety questionsObject and addressDeclarationsDereferencePointer parametersNull and invalid pointersArrays and arithmeticConst placementObject lifetimePrinting addressesIndependent labLesson reviewKnowledge check
Course contents