Topic 74 of 100 Pointers in C Interactive Tutorial

Pointers vs Arrays in C: Similarities, Differences, and Examples

12 mins read Last updated: 22 Aug 2026 24,900 views
C Compiler 0.00%

What is Pointers vs Arrays in C? Core Concept & Direct Summary

Core Objective & Problem Statement

What are the exact technical differences and similarities between pointers and arrays in C memory?

Direct Answer Summary

"Is an array just a pointer?" It's one of the most-asked questions in C, and the honest answer is the one that frustrates people: almost, but not quite — and the gap between those two is exactly where the bugs live.

Arrays and pointers share syntax, share behavior in most expressions, and get treated as interchangeable in function parameters. Yet they are fundamentally different objects with different memory layouts, different sizeof results, and different rules about what you can change.

This page draws the line precisely. First the similarities (why they feel the same), then the differences (the ones that matter), then the specific situations that cause confusion — with code that demonstrates each.

(Suggested image: the hero diagram — two panels. Left: an array as a fixed block of five adjacent cells with a name label spanning it. Right: a pointer as one small box containing an address, with an arrow pointing at a separate block. Same look at the arrow, different objects.)

How Does Pointers vs Arrays in C Work? In-Depth Mechanics & Architecture

Why They Feel the Same

Start with the parts that genuinely overlap, because they explain most of the confusion.

Similarity 1: The name gives an address

main.c
Run Online
int arr[5] = {
    10, 20, 30, 40, 50
}
;
int *p = arr;
// works! no & needed

The array name arr, used as a value, evaluates to the address of its first element. So p = arr and p = &arr[0] are the same assignment. This is the foundation of the whole relationship.

Similarity 2: Indexing works identically

main.c
Run Online
printf("%d\n", arr[2]);
// 30 — array notationprintf("%d\n", p[2]);
// 30 — SAME notation on a pointer!

Both give 30. You can index a pointer exactly like an array, because (as we covered in the pointer arithmetic tutorial) x[i] is defined as *(x + i) — and that definition applies to any pointer or array.

Similarity 3: Pointer arithmetic works on both

main.c
Run Online
printf("%d\n", *(arr + 2));
// 30printf("%d\n", *(p + 2));
// 30

Same mechanism, same result.

Similarity 4: Function parameters erase the distinction

main.c
Run Online
void f(int arr[]);
// looks like an arrayvoid f(int *arr);
// looks like a pointer
// These two declarations are IDENTICAL to the compiler.

So when you pass arr to f, you're passing a pointer either way. Inside f, there's no array anymore — just a pointer. This is the single biggest source of "arrays and pointers are the same" belief, because in functions, they are.

(Suggested image: the four similarities as small code cards with checkmarks, labeled "identical behavior")


Where They Genuinely Differ

Now the part that matters. Four real differences.

Difference 1: The array has its own storage — the pointer doesn't

main.c
Run Online
int arr[5] = {
    10, 20, 30, 40, 50
}
;
// 20 bytes allocated for the elementsint *p;                                  // 8 bytes (a 64-bit system) for an address                                         // and p currently points NOWHERE

An array owns its elements. A pointer is just an address holder; the memory it points to belongs to something else (or nothing at all, until assigned).

Practical consequence: you can't just have a pointer and start storing into it.

main.c
Run Online
int *p;
// uninitializedp[0] = 10;
// ✗ UNDEFINED BEHAVIOR — writing to a random addressp = arr;
// ✓ now it's valid — it points at real storage

This is the #1 practical difference. An array is usable immediately; a pointer needs a valid target first.

(Suggested image: array = boxes with values inside; uninitialized pointer = an arrow pointing at "????" with a warning symbol)

Difference 2: sizeof reports different things

main.c
Run Online
int arr[5] = {
    10, 20, 30, 40, 50
}
;
int *p = arr;
printf("%zu\n", sizeof(arr));
// 20 — the whole array (5 × 4 bytes)printf("%zu\n", sizeof(p));
// 8  — just the pointerprintf("%zu\n", sizeof(*p));
// 4  — one int (what p points at)

sizeof on an array gives the total bytes of all elements. sizeof on a pointer gives the pointer's own size — always 8 (64-bit) or 4 (32-bit), regardless of how big the array behind it is.

This is the difference with real consequences, covered extensively in the array-size tutorial:

main.c
Run Online
void bad(int arr[]) {
    int n = sizeof(arr) / sizeof(arr[0]);
    // ✗ 8/4 = 2 — MEANINGLESS    // the array's real size is long gone by now
}

The rule:sizeof knows the array only where the array was declared. Anywhere else — function parameters, pointers — it reports the pointer.

Difference 3: The pointer can be reassigned; the array name cannot

main.c
Run Online
int a[3] = {
    1, 2, 3
};
int b[3] = {
    4, 5, 6
};
int *p = a;
p = b;
// ✓ fine — now p points at bp++;                    // ✓ fine — p moves to b[1]p = NULL;               // ✓ fine
a = b;
// ✗ COMPILE ERROR — cannot assign to an array namea++;                    // ✗ COMPILE ERROR — array name is not a modifiable lvalue

This is the crucial distinction. The array name is a constant address — it references the same storage forever and can't be redirected. A pointer variable can be pointed anywhere, any time.

You can still change array elements:

main.c
Run Online
a[0] = 99;
// ✓ fine — modifying an element, not the array name

But you cannot make a refer to different memory. That's the line.

Difference 4: The compiler tracks the array's size

main.c
Run Online
int arr[5];
printf("%zu\n", sizeof(arr));
// 20 — compiler knows it's 5 ints
int *p;
printf("%zu\n", sizeof(p));
// 8 — size unknown, and unknowable

An array type includes its length. The compiler can validate things like int arr[5] and report sizeof accurately, and it may warn about obvious out-of-bounds access on a known-size local array. A pointer type carries no length information at all — the compiler can't warn you about anything.

(Suggested image: a four-row summary table as a graphic — the four differences with array behavior in one column and pointer behavior in the other)


The Comparison Table

Array int a[5]Pointer int *p
What it isA block of storage holding elementsA variable holding an address
Has its own memory?✓ Yes, for all elementsOnly for the address itself
sizeof givesTotal bytes of all elementsSize of the pointer (8/4)
Can be reassigned?✗ No — the name is fixed✓ Yes, to anything
Can use ++?✗ No✓ Yes
Usable immediately after declaration?Yes (though uninitialized = garbage values)No — must point somewhere valid first
Decays to a pointer?✓ Yes, in most expressionsN/A — it already is one
In a function parameterIdentical to a pointerIdentical to an array
Indexable?✓ Yes✓ Yes
Supports pointer arithmetic?✓ Yes (via decay)✓ Yes
Compiler knows the length?✓ Yes, where declared✗ No, ever

(Suggested image: this table styled as a clean infographic — the most linkable asset on the page)


The Key Idea: "Decay"

The mechanism behind most of the similarity is called decay, and it's worth naming precisely.

In most expressions, an array name is automatically converted to a pointer to its first element:

main.c
Run Online
int arr[5] = {
    10, 20, 30, 40, 50
}
;
printf("%d\n", arr[2]);
// arr decays to &arr[0], then indexing happensint *p = arr;
// decays here toofunc(arr);
// and hereint x = *(arr + 1);
// and here

Where an array does NOT decay — the three exceptions where it stays an array:

  1. As the operand of sizeof → sizeof(arr) gives the full array size
  2. As the operand of & → &arr is a pointer to the whole array (type int (*)[5], not int *)
  3. As a string literal initializing a char array → char s[] = "text"; copies into the array

Everywhere else, "the array" really means "a pointer to its first element." Understand the three exceptions and decay stops being mysterious.

A genuinely interesting consequence — &arr vs arr:

main.c
Run Online
int arr[5];
printf("%p\n",(void *)arr);
// address of arr[0]printf("%p\n", (void *)&arr);       // SAME address value!
// But they differ in TYPE, so arithmetic differs:printf("%p\n", (void *)(arr + 1));   // +4 bytes (next int)printf("%p\n", (void *)(&arr + 1));  // +20 bytes (past the WHOLE array — type is int(*)[5])

Same numeric address, different types, wildly different arithmetic. A subtle point that marks the boundary of array-versus-pointer understanding — and a favorite interview question.

(Suggested image: the arr vs &arr arrows over the same array — one arrow to element 0, one arrow spanning the whole block — with the +1 results shown differently)


Where the Confusion Bites: Five Demonstrations

1. Inside a function, it's always a pointer

main.c
Run Online
void f(int arr[]) {
    printf("%zu\n", sizeof(arr));
    // 8 — a pointer, not the array
}

Even though the parameter looks like an array, it's a pointer. This is why arrays need a separate size parameter.

2. You can't assign arrays to each other

main.c
Run Online
int a[3] = {
    1,2,3
}
;
int b[3];
b = a;
// ✗ compile error

Arrays aren't assignable in C. Copy element by element, or use memcpy. (Pointers assign freely — p = q works.)

3. Returning a local array doesn't work — but the failure is a pointer failure

main.c
Run Online
int *broken(void) {
    int local[5] = {
        1,2,3,4,5
    }
;
    return local;
    // ✗ returns a pointer to dead memory
}

The function can return a pointer. The problem is that the storage it points at no longer exists. This is the dangling-pointer trap — and it's specifically a pointer problem, not an array one.

4. sizeof changes as soon as you leave the declaring scope

main.c
Run Online
int arr[5];
void f(void) {
    // arr is a global here — still decays in expressions    printf("%zu\n", sizeof(arr));      // 20 — works for a file-scope array!}
    void g(int arr[]) {
        // local parameter — decayed    printf("%zu\n", sizeof(arr));      // 8 — pointer}

Globals are a partial exception (the compiler still sees the declaration), but parameters never are.

5. Strings: the boundary in practice

main.c
Run Online
char s1[] = "Hello";
// an ARRAY — writable, 6 bytes, own storagechar *s2 = "Hello";
// a POINTER to a literal — read-only, 8 bytes
s1[0] = 'J';
// ✓ fine — "Jello"s2[0] = 'J';
// ✗ crash — writing to read-only memory

Same-looking initializations, completely different objects. This is the array-pointer difference in its most practically dangerous form — the same trap from the strings basics tutorial.

(Suggested image: the five confusions as a numbered list graphic with the key line of code beside each)


A Complete Example: Same Result, Both Views

Here's a program showing how the two are used side by side, and where they must be used differently:

main.c
Run Online
#include <stdio.h>
// --- Array parameter: really a pointer, so size is separate ---
void printViaArray(const int arr[], int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
        // array notation
    }
    putchar('\n');
}
// --- Pointer parameter: identical mechanism, pointer notation ---
void printViaPointer(const int *p, int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", *(p + i));
        // pointer notation
    }
    putchar('\n');
}
// --- Sum using pointer walking (the pointer's home turf) ---
int sumWalk(const int *start, const int *end) {
    int total = 0;
    while (start < end) {
        // pointer comparison — only valid in-array
        total += *start;
        start++;
        // moving the LOCAL pointer copy is fine
    }
    return total;
}
int main(void) {
    int arr[6] = {
        10, 20, 30, 40, 50, 60
    };
    // --- Where arrays differ from pointers ---
    printf("sizeof(arr)    : %zu bytes (the whole array)\n", sizeof(arr));
    int *p = arr;
    printf("sizeof(p)      : %zu bytes (just the address)\n", sizeof(p));
    printf("sizeof(*p)     : %zu bytes (one element)\n", sizeof(*p));
    int n = sizeof(arr) / sizeof(arr[0]);
    // only valid on the array itself
    printf("Element count  : %d\n\n", n);
    // --- Identical results, different notations ---
    puts("Array notation  : ");
    printViaArray(arr, n);
    puts("Pointer notation: ");
    printViaPointer(arr, n);
    // --- The pointer's advantage: moving through without an index ---
    printf("Sum (pointer walk): %d\n", sumWalk(arr, arr + n));
    // --- The pointer's other advantage: reassignment ---
    int other[3] = {
        99, 88, 77
    };
    p = other;
    // ✓ legal — redirect the pointer
    printf("p redirected at other[0]: %d\n", p[0]);
    // arr = other;                             // ✗ would not compile — arrays can't be reassigned
    return 0;
}

Sample Run:

main.c
Run Online
sizeof(arr) : 24 bytes(the whole array)
sizeof(p) : 8 bytes(just the address)
sizeof(*p) : 4 bytes(one element)
Element count : 6
Array notation :
10 20 30 40 50 60
Pointer notation:
10 20 30 40 50 60
Sum(pointer walk): 210
p redirected at other[0]: 99

Everything from this page, demonstrated:

  • sizeof differs — 24 for the array, 8 for the pointer, from the same starting address
  • sizeof(arr)/sizeof(arr[0]) works only because arr is still a genuine array in main
  • Identical output from array notation and pointer notation
  • Pointer walking with start < end — legal comparison because both pointers are within the same array (plus one-past-the-end)
  • Reassignment works for p, and the commented-out arr = other shows the line that would not compile

Note the sumWalk function signature: it takes two pointers, and the caller computes arr + n as the end boundary. That's the pointer arithmetical view of a range — and it's exactly how std::begin/std::end ranges and many C library APIs are shaped.


Common Confusions and Their Fixes

  1. "Arrays and pointers are the same." They behave the same in most expressions, but an array has storage and a pointer holds an address. Different objects, similar usage.
  2. sizeof inside functions — always a pointer there. Pass the size.
  3. "I can reassign an array." No — the name is fixed. Use a pointer if you need to redirect.
  4. "A pointer has memory I can fill." Only after pointing it at real storage (an array, or malloc).
  5. "&arr is the same as arr." Same address value, different type, different arithmetic (+1 moves 4 bytes vs 20).
  6. Declaring int *p[5] vs int (*p)[5] — the first is an array of 5 pointers; the second is a pointer to an array of 5 ints. Both legal, completely different. (A whole topic in itself.)
  7. Comparing arr == p — legal (address comparison), and it may be true, but it compares addresses not contents. For contents, compare element by element.
  8. Assuming the compiler protects either one — neither arrays nor pointers are bounds-checked. The array's type carries length info, but nothing enforces access limits at runtime.

(Suggested image: "8 Confusions Fixed" checklist graphic — consistent with the series)


Final Thoughts

The honest summary, in one paragraph: an array is a block of storage; a pointer is a variable holding an address. They behave identically in most expressions because the array's name decays to a pointer to its first element — so indexing, arithmetic, and function passing all work the same. But they diverge precisely where it matters: sizeof (full size vs address size), assignment (fixed name vs reassignable), storage ownership (owned vs borrowed), and compiler knowledge of length (known vs unknown).

The exceptions to decay — sizeof, &, and string initialization — are the three places to keep in mind. Everything else follows.

And the practical payoff: knowing this distinction is what lets you write correct array functions (size passed separately), avoid the dangling-pointer return, and understand why char s[] = "text" and char *s = "text" are not interchangeable. That's a genuinely large fraction of C's difficult-beginner territory, resolved by one clear line.

Runnable C Code Example & Practical Implementation

main.c
Run Online
#include <stdio.h>
int main() {
    int arr[5] = {
        1, 2, 3, 4, 5
    };
    int *ptr = arr;
    printf("sizeof(arr): %zu bytes (Whole array)\
", sizeof(arr));
    printf("sizeof(ptr): %zu bytes (Pointer variable)\
", sizeof(ptr));
    printf("arr[2] == *(ptr + 2): %d\
", arr[2] == *(ptr + 2));
    return 0;
}

Key Takeaways, Best Practices & Memory Safety Rules

Best Practice & Key Takeaways
Remember that sizeof on an array returns the total array size, while sizeof on a pointer returns pointer width.

Frequently Asked Questions & Technical Interview Insights

Technical Interview & Viva Questions

Frequently asked C programming interview questions
Q

Q: Is an array strictly a constant pointer in C?

A: No, this is a common misconception. An array is a distinct type representing a contiguous block of objects; it decays to a pointer in expressions, but retains its true array size with `sizeof` and `&`.
Q

Q: Can you write `arr++` on an array name?

A: No! An array name is an immutable rvalue address; `arr++` is illegal.

Related Tools, Tutorials & Authoritative References

Recommended Interactive Tools

Authoritative Standards & Citations