In C, arrays and pointers are intimately connected—so much so that they can often be used interchangeably. The name of an array acts like a constant pointer to its first element, and pointer arithmetic provides an alternative way to access array elements. Understanding this relationship is crucial for mastering C, as it affects everything from simple array traversal to complex data structures and function interfaces.
The Fundamental Relationship
The key insight is that when you declare an array, the array name represents the address of the first element. This means:
arr[i] == *(arr + i)
Let's see this in action:
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
printf("Array name as pointer:\n");
printf("arr = %p\n", (void*)arr);
printf("&arr[0] = %p\n", (void*)&arr[0]);
printf("\nAccessing elements:\n");
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\t", i, arr[i]);
printf("*(arr + %d) = %d\t", i, *(arr + i));
printf("Address: %p\n", (void*)(arr + i));
}
return 0;
}
Output (addresses will vary):
Array name as pointer: arr = 0x7ffc12345670 &arr[0] = 0x7ffc12345670 Accessing elements: arr[0] = 10 *(arr + 0) = 10 Address: 0x7ffc12345670 arr[1] = 20 *(arr + 1) = 20 Address: 0x7ffc12345674 arr[2] = 30 *(arr + 2) = 30 Address: 0x7ffc12345678 arr[3] = 40 *(arr + 3) = 40 Address: 0x7ffc1234567c arr[4] = 50 *(arr + 4) = 50 Address: 0x7ffc12345680
Arrays Are Not Pointers (Important Distinction)
Despite their close relationship, arrays and pointers are not the same thing:
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;
printf("sizeof(arr) = %lu bytes\n", sizeof(arr)); // Size of entire array
printf("sizeof(ptr) = %lu bytes\n", sizeof(ptr)); // Size of pointer
printf("\narr[2] = %d\n", arr[2]);
printf("ptr[2] = %d\n", ptr[2]);
// Can modify pointer
ptr++;
printf("After ptr++, *ptr = %d (arr[1])\n", *ptr);
// Cannot modify array name
// arr++; // ERROR: arr is not a modifiable lvalue
return 0;
}
Output:
sizeof(arr) = 20 bytes sizeof(ptr) = 8 bytes arr[2] = 3 ptr[2] = 3 After ptr++, *ptr = 2 (arr[1])
Pointer Arithmetic with Arrays
Pointer arithmetic is the foundation of array access:
#include <stdio.h>
int main() {
int arr[] = {2, 4, 6, 8, 10, 12, 14, 16};
int *ptr = arr;
int *end = arr + 8; // Points one past the last element
printf("Array traversal with pointer arithmetic:\n");
while (ptr < end) {
printf("Current: %d, Next offset: %ld bytes\n",
*ptr, (char*)(ptr + 1) - (char*)ptr);
ptr++;
}
// Reset pointer
ptr = arr;
printf("\nAccess patterns:\n");
printf("*(ptr + 3) = %d\n", *(ptr + 3));
printf("ptr[3] = %d\n", ptr[3]);
printf("3[ptr] = %d (weird but works!)\n", 3[ptr]); // Equivalent to *(3 + ptr)
return 0;
}
Output:
Array traversal with pointer arithmetic: Current: 2, Next offset: 4 bytes Current: 4, Next offset: 4 bytes Current: 6, Next offset: 4 bytes Current: 8, Next offset: 4 bytes Current: 10, Next offset: 4 bytes Current: 12, Next offset: 4 bytes Current: 14, Next offset: 4 bytes Current: 16, Next offset: 4 bytes Access patterns: *(ptr + 3) = 8 ptr[3] = 8 3[ptr] = 8 (weird but works!)
Passing Arrays to Functions
When you pass an array to a function, you're actually passing a pointer:
#include <stdio.h>
// These three declarations are equivalent
void printArray1(int arr[], int size) {
printf("sizeof(arr) in function: %lu bytes\n", sizeof(arr)); // Pointer size!
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
void printArray2(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", *(arr + i));
}
printf("\n");
}
void printArray3(int arr[5], int size) { // Size in brackets is ignored
for (int i = 0; i < size; i++) {
printf("%d ", *(arr + i));
}
printf("\n");
}
void modifyArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // Modifies original array
}
}
int main() {
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);
printf("Original array: ");
printArray1(numbers, size);
modifyArray(numbers, size);
printf("After modification: ");
printArray2(numbers, size);
printf("\nArray in main, sizeof = %lu bytes\n", sizeof(numbers));
return 0;
}
Output:
Original array: 1 2 3 4 5 After modification: 2 4 6 8 10 Array in main, sizeof = 20 bytes sizeof(arr) in function: 8 bytes
Multidimensional Arrays and Pointers
Multidimensional arrays add another level of pointer relationships:
#include <stdio.h>
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
printf("matrix = %p (address of first row)\n", (void*)matrix);
printf("matrix[0] = %p (address of first element)\n", (void*)matrix[0]);
printf("&matrix[0][0] = %p\n", (void*)&matrix[0][0]);
printf("\nRow pointers:\n");
for (int i = 0; i < 3; i++) {
printf("matrix[%d] = %p\n", i, (void*)matrix[i]);
}
printf("\nElement access:\n");
printf("matrix[1][2] = %d\n", matrix[1][2]);
printf("*(*(matrix + 1) + 2) = %d\n", *(*(matrix + 1) + 2));
printf("*(matrix[1] + 2) = %d\n", *(matrix[1] + 2));
// Pointer to array (row pointer)
int (*rowPtr)[4] = matrix; // Pointer to array of 4 ints
printf("\nRow pointer arithmetic:\n");
printf("rowPtr points to row %d\n", *rowPtr[0]);
rowPtr++; // Move to next row
printf("After rowPtr++, points to row starting with %d\n", (*rowPtr)[0]);
return 0;
}
Output:
matrix = 0x7ffc12345670 matrix[0] = 0x7ffc12345670 &matrix[0][0] = 0x7ffc12345670 Row pointers: matrix[0] = 0x7ffc12345670 matrix[1] = 0x7ffc12345680 matrix[2] = 0x7ffc12345690 Element access: matrix[1][2] = 7 *(*(matrix + 1) + 2) = 7 *(matrix[1] + 2) = 7 Row pointer arithmetic: rowPtr points to row 1 After rowPtr++, points to row starting with 5
Pointer to Pointer vs 2D Array
There's an important distinction between 2D arrays and arrays of pointers:
#include <stdio.h>
#include <stdlib.h>
int main() {
// Method 1: True 2D array (contiguous memory)
int arr2D[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// Method 2: Array of pointers (non-contiguous)
int *arrOfPtrs[3];
for (int i = 0; i < 3; i++) {
arrOfPtrs[i] = malloc(4 * sizeof(int));
for (int j = 0; j < 4; j++) {
arrOfPtrs[i][j] = i * 4 + j + 1;
}
}
printf("True 2D array memory layout:\n");
printf("arr2D = %p\n", (void*)arr2D);
printf("arr2D[0] = %p\n", (void*)arr2D[0]);
printf("arr2D[1] = %p\n", (void*)arr2D[1]);
printf("arr2D[2] = %p\n", (void*)arr2D[2]);
printf("Distance between rows: %ld bytes\n",
(char*)arr2D[1] - (char*)arr2D[0]);
printf("\nArray of pointers memory layout:\n");
printf("arrOfPtrs = %p\n", (void*)arrOfPtrs);
printf("arrOfPtrs[0] = %p\n", (void*)arrOfPtrs[0]);
printf("arrOfPtrs[1] = %p\n", (void*)arrOfPtrs[1]);
printf("arrOfPtrs[2] = %p\n", (void*)arrOfPtrs[2]);
// Cleanup
for (int i = 0; i < 3; i++) {
free(arrOfPtrs[i]);
}
return 0;
}
Practical Examples
Example 1: Array Reversal with Pointers
#include <stdio.h>
void reverseArray(int *arr, int size) {
int *start = arr;
int *end = arr + size - 1;
while (start < end) {
// Swap using pointers
int temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
}
void printArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8};
int size = sizeof(arr) / sizeof(arr[0]);
printf("Original: ");
printArray(arr, size);
reverseArray(arr, size);
printf("Reversed: ");
printArray(arr, size);
return 0;
}
Output:
Original: 1 2 3 4 5 6 7 8 Reversed: 8 7 6 5 4 3 2 1
Example 2: Finding Array Boundaries
#include <stdio.h>
int isWithinArray(int *arr, int size, int *ptr) {
int *start = arr;
int *end = arr + size; // One past the last element
return (ptr >= start && ptr < end);
}
int main() {
int numbers[] = {10, 20, 30, 40, 50};
int size = sizeof(numbers) / sizeof(numbers[0]);
int *valid = &numbers[2];
int *invalid = &numbers[10]; // Out of bounds
printf("Array starts at: %p\n", (void*)numbers);
printf("Array ends at: %p\n", (void*)(numbers + size));
printf("Valid pointer: %p\n", (void*)valid);
printf("Invalid pointer: %p\n", (void*)invalid);
printf("\nValid pointer within array: %s\n",
isWithinArray(numbers, size, valid) ? "Yes" : "No");
printf("Invalid pointer within array: %s\n",
isWithinArray(numbers, size, invalid) ? "Yes" : "No");
return 0;
}
Example 3: Matrix Multiplication with Pointers
#include <stdio.h>
#include <stdlib.h>
void matrixMultiply(int *A, int *B, int *C,
int m, int n, int p) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < p; j++) {
int sum = 0;
for (int k = 0; k < n; k++) {
// A[i][k] * B[k][j]
sum += *(A + i * n + k) * *(B + k * p + j);
}
*(C + i * p + j) = sum;
}
}
}
void printMatrix(int *matrix, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%4d ", *(matrix + i * cols + j));
}
printf("\n");
}
}
int main() {
int A[2][3] = {{1, 2, 3}, {4, 5, 6}};
int B[3][2] = {{7, 8}, {9, 10}, {11, 12}};
int C[2][2];
printf("Matrix A (2x3):\n");
printMatrix(&A[0][0], 2, 3);
printf("\nMatrix B (3x2):\n");
printMatrix(&B[0][0], 3, 2);
matrixMultiply(&A[0][0], &B[0][0], &C[0][0], 2, 3, 2);
printf("\nResult C = A * B (2x2):\n");
printMatrix(&C[0][0], 2, 2);
return 0;
}
Output:
Matrix A (2x3): 1 2 3 4 5 6 Matrix B (3x2): 7 8 9 10 11 12 Result C = A * B (2x2): 58 64 139 154
Example 4: String Array with Pointers
#include <stdio.h>
#include <string.h>
void sortStrings(char *strings[], int count) {
for (int i = 0; i < count - 1; i++) {
for (int j = i + 1; j < count; j++) {
if (strcmp(strings[i], strings[j]) > 0) {
// Swap pointers, not strings
char *temp = strings[i];
strings[i] = strings[j];
strings[j] = temp;
}
}
}
}
void printStrings(char *strings[], int count) {
for (int i = 0; i < count; i++) {
printf(" %s\n", strings[i]);
}
}
int main() {
char *fruits[] = {
"Banana",
"Apple",
"Orange",
"Mango",
"Grape"
};
int count = sizeof(fruits) / sizeof(fruits[0]);
printf("Original order:\n");
printStrings(fruits, count);
sortStrings(fruits, count);
printf("\nSorted order:\n");
printStrings(fruits, count);
// Memory layout
printf("\nPointer array memory:\n");
for (int i = 0; i < count; i++) {
printf("fruits[%d] = %p -> \"%s\"\n",
i, (void*)fruits[i], fruits[i]);
}
return 0;
}
Output:
Original order: Banana Apple Orange Mango Grape Sorted order: Apple Banana Grape Mango Orange Pointer array memory: fruits[0] = 0x400624 -> "Apple" fruits[1] = 0x40062b -> "Banana" fruits[2] = 0x400646 -> "Grape" fruits[3] = 0x400637 -> "Mango" fruits[4] = 0x400632 -> "Orange"
Example 5: Dynamic 2D Array with Pointers
#include <stdio.h>
#include <stdlib.h>
int** createMatrix(int rows, int cols) {
int **matrix = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
matrix[i] = malloc(cols * sizeof(int));
}
return matrix;
}
void freeMatrix(int **matrix, int rows) {
for (int i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);
}
void fillMatrix(int **matrix, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = i * cols + j + 1;
}
}
}
void printMatrixPtr(int **matrix, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%4d ", matrix[i][j]);
}
printf("\n");
}
}
int main() {
int rows = 3, cols = 4;
int **matrix = createMatrix(rows, cols);
fillMatrix(matrix, rows, cols);
printf("Dynamic 2D array:\n");
printMatrixPtr(matrix, rows, cols);
printf("\nMemory layout:\n");
for (int i = 0; i < rows; i++) {
printf("matrix[%d] = %p -> row data at %p\n",
i, (void*)&matrix[i], (void*)matrix[i]);
}
freeMatrix(matrix, rows);
return 0;
}
Common Array-Pointer Idioms
1. Iterating with Pointers
#include <stdio.h>
int sumArray(int *arr, int size) {
int sum = 0;
int *end = arr + size;
for (int *p = arr; p < end; p++) {
sum += *p;
}
return sum;
}
int main() {
int numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int size = sizeof(numbers) / sizeof(numbers[0]);
printf("Sum: %d\n", sumArray(numbers, size));
return 0;
}
2. Finding Array Length
#include <stdio.h>
// Works only for actual arrays, not pointers
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
void processArray(int arr[], int size) {
// In function, arr is a pointer, so can't use macro
for (int i = 0; i < size; i++) {
// process arr[i]
}
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
printf("Array size: %lu\n", ARRAY_SIZE(arr)); // Works
int *ptr = arr;
printf("Pointer 'size': %lu\n", ARRAY_SIZE(ptr)); // Wrong! Gives 1 or 2
return 0;
}
3. Pointer Difference for Index
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50, 60, 70};
int *ptr = &arr[3]; // Points to 40
// Find index using pointer arithmetic
int index = ptr - arr;
printf("ptr points to arr[%d] = %d\n", index, *ptr);
// Traverse from pointer
printf("From pointer to end: ");
for (int *p = ptr; p < arr + 7; p++) {
printf("%d ", *p);
}
printf("\n");
return 0;
}
Common Pitfalls
1. Confusing Array of Pointers with 2D Array
#include <stdio.h>
#include <stdlib.h>
int main() {
// This is a 2D array (contiguous)
int arr2D[3][3];
// This is an array of pointers (non-contiguous)
int *ptrArray[3];
for (int i = 0; i < 3; i++) {
ptrArray[i] = malloc(3 * sizeof(int));
}
printf("Size of arr2D: %lu bytes\n", sizeof(arr2D));
printf("Size of ptrArray: %lu bytes\n", sizeof(ptrArray));
// They are accessed differently in functions
// void func(int arr[][3]) // For arr2D
// void func(int **arr) // For ptrArray (not the same!)
// Cleanup
for (int i = 0; i < 3; i++) {
free(ptrArray[i]);
}
return 0;
}
2. Assuming Pointer Arithmetic Works on void*
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
void *vp = arr;
// vp++; // ERROR in standard C (GCC extension allows it)
// Must cast first
int *ip = (int*)vp;
ip++;
printf("Second element: %d\n", *ip);
return 0;
}
3. Forgetting That Array Parameters Are Pointers
#include <stdio.h>
void badFunction(int arr[]) {
// This doesn't work as expected
int size = sizeof(arr) / sizeof(arr[0]); // arr is a pointer!
printf("In function, size = %d (wrong!)\n", size);
}
void goodFunction(int arr[], int size) {
printf("In function, with passed size = %d\n", size);
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
badFunction(arr);
goodFunction(arr, size);
return 0;
}
Performance Considerations
Pointer arithmetic can be more efficient than array indexing:
#include <stdio.h>
#include <time.h>
#define SIZE 10000000
int main() {
static int arr[SIZE];
clock_t start, end;
// Initialize
for (int i = 0; i < SIZE; i++) {
arr[i] = i;
}
// Array indexing
start = clock();
int sum1 = 0;
for (int i = 0; i < SIZE; i++) {
sum1 += arr[i];
}
end = clock();
double time1 = (double)(end - start) / CLOCKS_PER_SEC;
// Pointer arithmetic
start = clock();
int sum2 = 0;
int *ptr = arr;
int *endPtr = arr + SIZE;
while (ptr < endPtr) {
sum2 += *ptr++;
}
end = clock();
double time2 = (double)(end - start) / CLOCKS_PER_SEC;
printf("Array indexing: %.3f seconds\n", time1);
printf("Pointer arithmetic: %.3f seconds\n", time2);
return 0;
}
Summary Table
| Operation | Array Syntax | Pointer Syntax |
|---|---|---|
| Access element i | arr[i] | *(ptr + i) |
| Address of element i | &arr[i] | ptr + i |
| First element | arr[0] | *ptr |
| Move to next | N/A | ptr++ |
| Size | sizeof(arr) | sizeof(ptr) |
| Can be modified? | No | Yes |
| Pass to function | func(arr) | func(ptr) |
Best Practices
- Use array syntax for static arrays - More readable
- Use pointer arithmetic for traversal - Often more efficient
- Pass size explicitly - Arrays decay to pointers in functions
- Be consistent - Mixing styles can confuse
- Use const when appropriate - Protect data that shouldn't change
- Check boundaries - Prevent buffer overflows
- Understand the differences - Arrays and pointers are not the same
Conclusion
The relationship between arrays and pointers is fundamental to C programming. While they can often be used interchangeably, understanding their differences is crucial:
- Arrays are fixed-size blocks of memory; the array name is a constant address
- Pointers are variables that store addresses; they can be modified
- Array indexing
arr[i]is syntactic sugar for pointer arithmetic*(arr + i) - Passing arrays to functions passes a pointer, losing size information
- Multidimensional arrays have more complex pointer relationships
Mastering the array-pointer relationship enables you to:
- Write more efficient code
- Implement complex data structures
- Interface with system APIs
- Understand C's memory model
- Debug pointer-related issues
This duality is what makes C both powerful and challenging. Embrace it, and you'll unlock the full potential of the language.