Pointer Arithmetic & Arrays
In C, arrays and pointers are closely linked. When you pass an array to a function, write marks[i], or loop through a string, pointers are working underneath. Understanding this link explains several C rules that otherwise seem strange, such as why sizeof behaves differently inside a function.
In this lesson you will learn how an array name turns into a pointer, how adding to a pointer moves it from element to element, how to subtract pointers, and how to work with arrays of strings.
An array name becomes a pointer
In almost every expression, the name of an array is converted to a pointer to its first element. This conversion is often called decay. So int *p = marks; means exactly the same as int *p = &marks[0];.
Pointer arithmetic then lets you move through the array. Adding 1 to a pointer does not add 1 byte; it moves to the next element, so the address grows by sizeof one element. For an int array, that is 4 bytes.
#include <stdio.h>
int main(void)
{
int marks[4] = {78, 85, 91, 66};
int *p = marks; // same as &marks[0]
for (int i = 0; i < 4; i++) {
printf("p + %d = %p, *(p + %d) = %d, marks[%d] = %d\n",
i, (void *) (p + i), i, *(p + i), i, marks[i]);
}
return 0;
}A typical run on 64-bit Linux prints:
p + 0 = 0x7ffd2a6c3f30, *(p + 0) = 78, marks[0] = 78 p + 1 = 0x7ffd2a6c3f34, *(p + 1) = 85, marks[1] = 85 p + 2 = 0x7ffd2a6c3f38, *(p + 2) = 91, marks[2] = 91 p + 3 = 0x7ffd2a6c3f3c, *(p + 3) = 66, marks[3] = 66
Your addresses will differ, but they always go up by 4 (in hexadecimal, ...30, ...34, ...38, ...3c). For a double array they would go up by 8. The output also shows the key rule of C: a[i] is defined as *(a + i). The square-bracket syntax is just a convenient way of writing pointer arithmetic.
Walking through an array with a pointer
Instead of an index, you can move a pointer from the first element to the end. A pointer to the position just after the last element is allowed as an end marker, as long as you never dereference it.
#include <stdio.h>
int total(const int *begin, const int *end)
{
int sum = 0;
for (const int *q = begin; q < end; q++) {
sum += *q;
}
return sum;
}
int main(void)
{
int sales[] = {1200, 850, 1430, 990, 1530};
size_t n = sizeof sales / sizeof sales[0];
printf("Week total: Rs %d\n", total(sales, sales + n));
printf("First three days: Rs %d\n", total(sales, sales + 3));
return 0;
}Week total: Rs 6000 First three days: Rs 3480
sales + n points one past the end, so the loop stops just in time. Passing a begin and an end pointer makes it easy to work on part of an array, as the second call shows. Both index loops and pointer loops are correct; use whichever is clearer.
You will often see the idiom *p++. Postfix ++ binds more tightly than *, so *p++ means "use the value p points to, then move p to the next element".
Subtracting pointers
Subtracting two pointers into the same array gives the number of elements between them. The result has the type ptrdiff_t (from stddef.h), printed with %td. This is how you turn a found position back into an index.
#include <stddef.h>
#include <stdio.h>
size_t my_strlen(const char *s)
{
const char *p = s;
while (*p != '\0') {
p++;
}
return (size_t) (p - s);
}
int main(void)
{
const char *city = "Thiruvananthapuram";
printf("%s has %zu letters\n", city, my_strlen(city));
int seats[10] = {};
int *first = &seats[2];
int *last = &seats[7];
ptrdiff_t gap = last - first;
printf("Elements from seat 2 to seat 7: %td\n", gap);
return 0;
}Thiruvananthapuram has 18 letters Elements from seat 2 to seat 7: 5
my_strlen moves p to the terminating '\0', and p - s is the number of characters passed. This is essentially how the real strlen works. Subtracting or comparing pointers into different arrays is undefined behaviour, so only do it within one array.
Arrays and pointers are not identical
Decay makes arrays and pointers look the same, but they are different things:
- An array is a block of elements; a pointer only holds an address.
sizeofan array gives the size of all its elements;sizeofa pointer gives 8 on 64-bit systems.- You can make a pointer point somewhere else (
p = other;), but you cannot assign to an array name.
A function parameter declared as an array is really a pointer. That is why the length must be passed separately:
#include <stdio.h>
void show_size(int values[]) // really: int *values
{
printf("Inside the function: %zu bytes\n", sizeof values);
}
int main(void)
{
int values[4] = {1, 2, 3, 4};
printf("In main: %zu bytes\n", sizeof values);
show_size(values);
return 0;
}In main: 16 bytes Inside the function: 8 bytes
GCC even warns that sizeof on an array parameter returns the size of int *. The output shown is from a 64-bit system.
Arrays of strings
An array of const char * holds one pointer per string, which is the usual way to store a list of fixed words or names:
#include <stdio.h>
#include <string.h>
int main(void)
{
const char *stations[] = {"Delhi", "Agra", "Gwalior", "Bhopal"};
size_t n = sizeof stations / sizeof stations[0];
for (size_t i = 0; i < n; i++) {
printf("Stop %zu: %s (%zu letters, starts with %c)\n",
i + 1, stations[i], strlen(stations[i]), stations[i][0]);
}
return 0;
}Stop 1: Delhi (5 letters, starts with D) Stop 2: Agra (4 letters, starts with A) Stop 3: Gwalior (7 letters, starts with G) Stop 4: Bhopal (6 letters, starts with B)
stations[i] is a pointer to one string, and stations[i][0] is that string's first character. The strings themselves are read-only literals. If you need to change the text, use a two-dimensional char array instead, such as char names[4][20];, where each row is a 20-character buffer you own.
Reversing an array with two pointers
Two pointers moving towards each other solve many array problems neatly. Here end starts one past the last element:
#include <stdio.h>
void reverse(int *begin, int *end)
{
while (begin < end) {
end--;
int temp = *begin;
*begin = *end;
*end = temp;
begin++;
}
}
int main(void)
{
int runs[] = {10, 20, 30, 40, 50};
size_t n = sizeof runs / sizeof runs[0];
reverse(runs, runs + n);
printf("Reversed:");
for (size_t i = 0; i < n; i++) {
printf(" %d", runs[i]);
}
printf("\n");
return 0;
}Reversed: 50 40 30 20 10
Common mistakes
- Going past one-past-the-end. You may point one element past the end, but not further, and you may never dereference that end pointer.
- Using
sizeofon an array parameter. It gives the pointer size. Pass the length or an end pointer. - Comparing pointers from different arrays. Only compare or subtract pointers within the same array.
- Arithmetic on
void *. Standard C does not allowp + 1on avoid *, because the element size is unknown. Convert to a typed pointer first. - Misreading
*p++. It moves the pointer, not the value. To increment the value, write(*p)++.
Key points
- An array name decays to a pointer to its first element:
int *p = arr;. p + 1moves by one element, not one byte, anda[i]means*(a + i).- A pointer one past the last element is a valid end marker but must not be dereferenced.
- Subtracting pointers in one array gives the element distance as a
ptrdiff_t(print with%td). - Array parameters are pointers, so
sizeofinside the function gives the pointer size. const char *names[]stores a list of read-only strings; usechar names[n][len]for editable text.
Exercise
Write a function int *find_value(int *begin, int *end, int target) that returns a pointer to the first element equal to target, or nullptr if there is none. In main, search the roll numbers 104, 117, 121, 133 and 140 for 121 and for 150, and use pointer subtraction to print the index of a match.
Show solution
#include <stdio.h>
int *find_value(int *begin, int *end, int target)
{
for (int *p = begin; p < end; p++) {
if (*p == target) {
return p;
}
}
return nullptr;
}
int main(void)
{
int rolls[] = {104, 117, 121, 133, 140};
size_t n = sizeof rolls / sizeof rolls[0];
int targets[] = {121, 150};
for (int i = 0; i < 2; i++) {
int *hit = find_value(rolls, rolls + n, targets[i]);
if (hit != nullptr) {
printf("%d found at index %td\n", targets[i], hit - rolls);
} else {
printf("%d not found\n", targets[i]);
}
}
return 0;
}121 found at index 2 150 not found
hit - rolls counts the elements between the start of the array and the match, which is exactly its index.
Quick Quiz
1. If int a[5] starts at address 1000 and int is 4 bytes, what address is a + 3?
Adding 3 moves three elements of 4 bytes each: 1000 + 12 = 1012.
2. Which expression is always the same as arr[i]?
C defines arr[i] as *(arr + i); *arr + i adds i to the first element instead.
3. What does sizeof give for an array parameter inside a function on a 64-bit system?
An array parameter is really a pointer, so sizeof measures the pointer.
4. What is the type of the difference between two pointers into the same array?
Pointer subtraction gives a signed element count of type ptrdiff_t.
Learn C with a mentor
Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
