Hello! Welcome to your next lesson.
In our last session, we established that C arrays, both 1D and 2D, are stored as a single, contiguous block of memory laid out in row-major order. This understanding of memory layout is the foundation for today's topic.
We are now going to tackle one of the most classic and revealing C interview questions: "What is the difference between an array and a pointer?". A crisp, accurate answer demonstrates a deep understanding of C's memory model.
Your learning outcome for this lesson is to distinguish between array names and pointers, explaining when they are equivalent and when they differ (e.g., sizeof, address-of operator).
The Rule of "Decay": When They Seem the Same
The primary source of confusion is that in most C expressions, an array name is automatically converted, or "decays," into a pointer to its first element.
Consider this code:
#include <stdio.h>
int main(void) {
int arr[5] = {10, 20, 30, 40, 50};
int *p;
// The array 'arr' decays to a pointer to its first element.
p = arr;
// Both expressions access the element at index 2.
printf("arr[2] = %d\n", arr[2]);
printf("p[2] = %d\n", p[2]);
// Pointer arithmetic works on both.
printf("*(arr + 2) = %d\n", *(arr + 2));
printf("*(p + 2) = %d\n", *(p + 2));
return 0;
}
Because of this decay, arr and p can often be used interchangeably for element access. But this is where the similarity ends. An array name is not a pointer. Let's explore the exceptions to the decay rule, which reveal their fundamental differences.
The Exceptions: Where They Are Fundamentally Different
There are specific contexts where an array name does not decay into a pointer. These are the key to understanding the distinction.
Arrays and pointers - DEV Community
The following article, 'Arrays and pointers', provides a good starting point by demonstrating the common illusion of equivalence and then immediately showing where it breaks down. It clearly explains the rule of array decay and its key exceptions.
Please read the section titled 'Arrays can be pointers'. It introduces the general rule of decay and explicitly lists the exceptions: sizeof(), the address-of operator &, and string literal initializations. We will explore the first two exceptions in detail now.
Exception 1: The sizeof Operator
The sizeof operator is your most direct tool for proving that an array and a pointer are different types.
- When applied to an array,
sizeofreturns the total memory occupied by all elements of the array. - When applied to a pointer,
sizeofreturns the memory required to store the pointer itself (i.e., to hold a memory address), which is constant for a given system architecture (e.g., 4 bytes on a 32-bit system, 8 bytes on a 64-bit system).
Let's see this in action:
#include <stdio.h>
int main(void) {
int arr[10];
int *p = arr; // 'arr' decays here
// sizeof(arr) is the size of the whole array
// sizeof(int) * 10 = 4 * 10 = 40 (on a system with 4-byte ints)
printf("sizeof(arr) = %zu bytes\n", sizeof(arr));
// sizeof(p) is the size of a pointer variable
// This will be 4 or 8 depending on the architecture (32-bit vs 64-bit)
printf("sizeof(p) = %zu bytes\n", sizeof(p));
return 0;
}
This output unequivocally shows that the compiler has different information about arr (it knows it's a 40-byte object) and p (it knows it's an 8-byte pointer variable on a 64-bit system).
Exception 2: The Unary & (Address-of) Operator
This exception is more subtle and a favorite in technical interviews.
- Applying
&to a pointer gives you the address of the pointer variable itself (a pointer to a pointer). - Applying
&to an array name gives you the address of the entire array.
While the address value of arr and &arr is the same (the array starts at its own address), their types are critically different:
arr(after decay) has the typeint *(pointer to an integer).&arrhas the typeint (*)[10](pointer to an array of 10 integers).
This type difference has a profound impact on pointer arithmetic:
#include <stdio.h>
int main(void) {
int arr[10];
int *p = arr;
int (*ptr_to_array)[10] = &arr;
printf("p = %p\n", (void*)p);
printf("p + 1 = %p (increments by sizeof(int))\n", (void*)(p + 1));
printf("\n");
printf("ptr_to_array = %p\n", (void*)ptr_to_array);
printf("ptr_to_array+1 = %p (increments by sizeof(arr))\n", (void*)(ptr_to_array + 1));
return 0;
}
Expected Output (on a 64-bit machine with 4-byte ints):
p = 0x7ffc12345670
p + 1 = 0x7ffc12345674 (increments by 4 bytes)
ptr_to_array = 0x7ffc12345670
ptr_to_array+1 = 0x7ffc12345698 (increments by 40 bytes)
Incrementing p moves it to the next int. Incrementing ptr_to_array moves it by the size of the entire 10-element array.
The Core Reason: Compile-Time Labels vs. Runtime Variables
So why do these differences exist? The fundamental distinction lies in what the compiler knows and when.
- An array name is essentially a compile-time constant. The compiler sees
arrand replaces it with the fixed memory address where the array begins. The namearris a label for a block of memory; it is not a variable that stores an address. This is why an expression likearr++is illegal—you cannot change the value of a label.arris not a modifiable l-value. - A pointer is a variable. It occupies its own space in memory and its value (the address it points to) can be changed at runtime. This is why
p++is a perfectly valid operation.
Are pointers and arrays equivalent in C?
Eli Bendersky's article 'Are pointers and arrays equivalent in C?' provides an excellent, low-level explanation of this core difference. It uses disassembly to show you exactly how the CPU handles array access versus pointer access, which will resonate with your systems-level experience.
Please read the sections 'So how are they different?' and 'A graphical explanation'. Focus on how array access is a direct offset from a compile-time label, while pointer access involves an extra step of first reading the pointer's value (an address) from memory.
Consequence: Passing Arrays to Functions
The most common situation where you see array decay is in function calls. When you pass an array to a function, what is actually passed is a pointer to the first element.
This is why these function declarations are equivalent to the compiler:
void myFunction(int arr[10]);
void myFunction(int arr[]);
void myFunction(int *arr);
Inside myFunction, arr is treated as a pointer variable (int *). This is a practical necessity: a function must be able to accept arrays of different sizes at runtime. The compiler can't hardcode the size and address as it does for a local array.
This explains a classic C "gotcha":
#include <stdio.h>
void checkSize(int arr[]) {
// Here, 'arr' has decayed to a pointer.
// sizeof(arr) will give the size of a pointer, not the array.
printf("sizeof in function: %zu\n", sizeof(arr));
}
int main(void) {
int myArray[20];
printf("sizeof in main: %zu\n", sizeof(myArray)); // Will be 80
checkSize(myArray); // Will be 8 (on a 64-bit system)
return 0;
}
This is precisely why you must pass the size of an array to a function as a separate argument if the function needs to know it.
Conclusion
You can now confidently answer the interview question. An array is not a pointer. An array is a contiguous block of data, while a pointer is a variable that holds a memory address. While an array name often decays to a pointer to its first element, they are fundamentally different types, as revealed by the sizeof and & operators.
Key Takeaways:
- Rule of Decay: In most expressions, an array name is converted to a pointer to its first element.
- Key Differences:
sizeof(array)gives the size of the entire array;sizeof(pointer)gives the size of the pointer variable.&arraygives a pointer to the entire array (e.g.,T (*)[N]);&pointergives a pointer to the pointer variable (e.g.,T **).- An array name is a non-modifiable l-value (a compile-time constant address); a pointer is a modifiable l-value (a variable).
- The "Why": An array name is a compile-time label for a memory block. A pointer is a variable that stores an address.
- Consequence: When passed to a function, an array always decays to a pointer, losing its size information.
In our next lesson, we'll build directly on this by examining the crucial difference between char arr[] = "string"; and char *p = "string";. We'll see how your understanding of array-pointer distinctions applies to memory location (stack vs. read-only data segment) and mutability.