C Memory Address

A memory address is the location of an object in your program’s memory. In C, understanding addresses matters because pointers, arrays, strings, dynamic memory, and function parameters all depend on the idea that values live somewhere, not just that they have names.

The address of a variable is not usually useful as a number to calculate with directly. It is useful because it lets the program refer to the exact storage location of an object.

Overview: How Memory Addresses Work

When a C program runs, objects such as local variables, arrays, string literals, and dynamically allocated blocks occupy bytes in memory. A variable name like age is a source-code label for one object. The expression &age produces the address of that object: a value that identifies where the object is stored.

C exposes addresses through pointer types. If age is an int, then &age has type int *, which means “pointer to int.” If price is a double, then &price has type double *. The type attached to an address is important because the compiler must know how many bytes belong to the object and how those bytes should be interpreted.

An address value is commonly displayed with the %p format specifier in printf, after converting it to void *. The printed form is implementation-defined: it might look hexadecimal, it might include a prefix such as 0x, and it will usually change between program runs. For that reason, examples that need exact output should not depend on the literal address text.

Memory addresses are not the same as ordinary integers in portable C. You can compare addresses that point into the same object or array in well-defined ways, and you can subtract two pointers into the same array to find an element distance. But treating addresses as general arithmetic numbers is not portable and often leads to undefined behavior.

Object lifetime also matters. The address of a local variable is valid only while that variable exists. A local automatic variable inside a function stops existing when the function returns, so returning its address leaves the caller with an invalid pointer. Later lessons use this rule heavily when discussing dynamic memory.

Syntax

type variable = value;
type *pointer = &variable;
printf("%p\n", (void *)&variable);
Syntax Meaning
&variable Get the memory address of variable.
type * A pointer type that can store the address of a type object.
%p The printf format for displaying a pointer value.
(void *) The portable conversion used when passing a pointer to %p.
&array[i] The address of element i inside an array.
p2 - p1 The number of elements between two pointers into the same array.

The address-of operator & is unary: it applies to one object expression. Do not confuse it with the binary bitwise AND operator, which also uses & but has two operands, such as a & b.

Examples

Store And Compare A Variable’s Address

#include <stdio.h>

int main(void)
{
    int count = 12;
    int *count_address = &count;

    printf("Value: %d\n", count);
    printf("Same address: %s\n", count_address == &count ? "yes" : "no");
    printf("Pointer storage bytes: %zu\n", sizeof count_address);

    return 0;
}

Output:

Value: 12
Same address: yes
Pointer storage bytes: 8

The variable count_address stores the address produced by &count. Instead of printing the raw address, the program compares the stored address with &count, which gives deterministic output. The pointer size shown here is typical on a 64-bit system; if your environment uses a different pointer size, that line may differ.

Addresses Of Array Elements

#include <stdio.h>
#include <stddef.h>

int main(void)
{
    int scores[] = {80, 85, 90, 95};
    int *first = &scores[0];
    int *third = &scores[2];
    ptrdiff_t distance = third - first;

    printf("First value: %d\n", *first);
    printf("Third value: %d\n", *third);
    printf("Elements between addresses: %td\n", distance);
    printf("Array name matches first element: %s\n", scores == &scores[0] ? "yes" : "no");

    return 0;
}

Output:

First value: 80
Third value: 90
Elements between addresses: 2
Array name matches first element: yes

Array elements are stored contiguously. The address of scores[2] is two int elements after the address of scores[0], so subtracting those two pointers gives 2. Pointer subtraction counts elements, not bytes.

Pass An Address To Update A Caller Variable

#include <stdio.h>

void add_tax_cents(int *amount_cents, int tax_cents)
{
    *amount_cents += tax_cents;
}

int main(void)
{
    int total = 2500;

    printf("Before: %d cents\n", total);
    add_tax_cents(&total, 180);
    printf("After: %d cents\n", total);

    return 0;
}

Output:

Before: 2500 cents
After: 2680 cents

C normally passes function arguments by value, so a function receives copies. Passing &total gives the function the address of the caller’s object. The function can then dereference that address and modify the original total.

Print An Address When You Need To Inspect It

#include <stdio.h>

int main(void)
{
    int level = 7;

    printf("Level value: %d\n", level);
    printf("Address was printed with %%p: yes\n");

    return 0;
}

Output:

Level value: 7
Address was printed with %p: yes

For real debugging, you could write printf("%p\n", (void *)&level);. This lesson does not show that line as a runnable expected-output example because the actual address text changes between systems and runs. The important rule is to use %p with a void * conversion, not %d or %u.

How It Works Step By Step

  1. The declaration int count = 12; creates an int object and stores 12 in its bytes.
  2. The expression &count asks for the address of that object.
  3. The declaration int *count_address = &count; stores that address in a pointer variable.
  4. When the program compares count_address == &count, it checks whether both pointer values identify the same object.
  5. When the program evaluates *count_address, it follows the address and accesses the int object stored there.

For arrays, C can compute an element address from the start of the array, the element index, and the size of each element. Conceptually, &scores[2] is the location of the first element plus two int positions. The compiler performs this calculation using the pointer type, which is why int * arithmetic moves in units of int and double * arithmetic moves in units of double.

Common Mistakes

Printing Addresses With The Wrong Format

Wrong code often looks like printf("%d", &x);. An address is not an int, and using the wrong format specifier has undefined behavior. Use printf("%p", (void *)&x); when you need to display a pointer value.

Expecting The Same Address Every Run

Modern systems often randomize where a program is loaded in memory. Even without randomization, compiler choices and environment details can move objects. Compare addresses for relationships inside the same run; do not write program logic that depends on a specific printed address.

Returning The Address Of A Local Variable

A function must not return &local when local is an automatic variable declared inside that function. The object stops existing at return time, so the caller receives an address that no longer names a valid object. Return a value, write into caller-provided storage, or use dynamic allocation when appropriate.

Doing Arbitrary Address Arithmetic

Pointer arithmetic is defined for elements of the same array object and for the one-past-the-end position. Adding random numbers to addresses or subtracting unrelated pointers is not portable C and can easily become undefined behavior.

Best Practices

  • Use & only when you need an object’s address, such as initializing a pointer or passing an output parameter.
  • Use %p and cast to void * when displaying an address with printf.
  • Do not depend on the exact text or numeric value of a printed address.
  • Keep pointer types matched to the objects whose addresses they store.
  • Only compare or subtract pointers when the comparison is meaningful and allowed, such as pointers into the same array.
  • Never use an address after the pointed-to object’s lifetime has ended.
  • Prefer clear pointer names, such as count_ptr or first, when the variable stores an address.

Practice Exercises

  1. Create an int variable, store its address in an int *, and print whether the stored pointer equals &variable.
  2. Create an array of five double values. Store the addresses of the first and last elements, subtract them, and print the element distance.
  3. Write a function apply_discount(int *price_cents, int discount_cents) that receives the address of a price and decreases the caller’s value.

Summary

  • A memory address identifies where an object is stored while it exists.
  • The address-of operator & produces the address of an object.
  • The type of an address is a pointer type, such as int * or double *.
  • Use %p with (void *) to print pointer values for debugging.
  • Array element addresses are contiguous, and pointer differences inside one array count elements.
  • Addresses are useful only while they point to valid objects with the correct lifetime and type.