C Pointers & Arrays
Relationship Between Pointers and Arrays
In C, arrays and pointers are closely related. In most expressions, an array "decays" to a pointer to its first element.
Key relationships (after decay):
- array_name ≈ &array_name[0]
- array_name[i] ≡ *(array_name + i)
- &array_name[i] ≡ array_name + i
Important: the array itself is not a pointer (it's a distinct type and not assignable); it only decays to a pointer in expressions.
Array-to-Pointer Decay: Common Exceptions
Array-to-pointer decay does NOT happen in a few cases:
• With sizeof: sizeof array yields total bytes of the array
• With unary &: &array yields a pointer to the whole array (type like int (*)[N])
• When initializing another array from a string literal (e.g., char s[] = "hi";)
#include <stdio.h>
int main(void){
int a[5];
printf("sizeof a: %zu\n", sizeof a); // total size of array
printf("&a type is pointer-to-array; sizeof &a: %zu\n", sizeof &a);
return 0;
}
sizeof a: 20 &a type is pointer-to-array; sizeof &a: 8
Array Access Using Pointers
#include <stdio.h>
int main(void){
int numbers[] = {10, 20, 30, 40, 50};
int *ptr = numbers; // decay to &numbers[0]
printf("Element 0: %d %d\n", numbers[0], *ptr);
printf("Element 1: %d %d\n", numbers[1], *(ptr + 1));
printf("Element 2: %d\n", *(numbers + 2));
return 0;
}
Element 0: 10 10 Element 1: 20 20 Element 2: 30
Pointer Arithmetic with Arrays
Pointer arithmetic traverses arrays by element size automatically (compiler scales by sizeof(*ptr)).
#include <stdio.h>
int main(void){
float values[] = {1.1f, 2.2f, 3.3f, 4.4f, 5.5f};
float *ptr = values;
for (int i = 0; i < 5; i++){
printf("Element %d: %.1f at address %p\n", i, *(ptr + i), (void*)(ptr + i));
}
return 0;
}
Element 0: 1.1 at address 0x7ffd4f9c7b20 Element 1: 2.2 at address 0x7ffd4f9c7b24 Element 2: 3.3 at address 0x7ffd4f9c7b28 Element 3: 4.4 at address 0x7ffd4f9c7b2c Element 4: 5.5 at address 0x7ffd4f9c7b30
Array Parameters as Pointers
When arrays are passed to functions, they decay to pointers. The function receives a pointer to the first element, not a copy of the array.
#include <stdio.h>
void printArray(int *arr, int size){
for (int i = 0; i < size; i++) printf("%d ", arr[i]);
}
void printArrayBracket(int arr[], int size){ // same as int *arr
for (int i = 0; i < size; i++) printf("%d ", *(arr + i));
}
int main(void){
int numbers[] = {1,2,3,4,5};
printArray(numbers, 5);
printf("\n");
printArrayBracket(numbers, 5);
return 0;
}
1 2 3 4 5 1 2 3 4 5
Pointer to Array vs Array of Pointers
These are different types:
• Array of pointers: each element is an independent pointer (e.g., int *ptrs[3])
• Pointer to array: points to an entire array object (e.g., int (*p)[3])
Pointer arithmetic differs: (p+1) with p of type int (*)[3] advances by 3 ints.
#include <stdio.h>
int main(void){
int a=1,b=2,c=3;
int *ptrArray[3] = {&a,&b,&c}; // array of pointers
int arr[3] = {10,20,30};
int (*arrayPtr)[3] = &arr; // pointer to array of 3 ints
printf("Array of pointers: ");
for (int i=0;i<3;i++) printf("%d ", *ptrArray[i]);
printf("\nPointer to array: ");
for (int i=0;i<3;i++) printf("%d ", (*arrayPtr)[i]);
return 0;
}
Array of pointers: 1 2 3 Pointer to array: 10 20 30
Best Practices & Pitfalls
1. Do not write arr = other; arrays are not assignable (use memcpy or loops).
2. Keep pointer arithmetic within the array bounds (or one past the end) to avoid undefined behavior.
3. Prefer passing lengths alongside array pointers; do not rely on sentinel values unless intentional.
4. Remember the decay exceptions (sizeof, unary &, array initialization) to avoid type surprises.
5. Use const where appropriate (e.g., const int *p) when you don’t intend to modify elements.