What is Pointer Arithmetic in C – C Programming Tutorials For Beginners

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Free C Programming Course · Pointers · Lecture 8

Pointer Arithmetic in C

Learn which operations are legal on pointers, how the compiler scales each step by the element size, and why it matters in embedded C programming.

What You Will Learn

This lecture of our free C programming course introduces pointer arithmetic. In this free C tutorial you will see why p + 1 does not always mean “one byte further”, how int, float, double and char pointers step differently, and how to prove it with a short program. These ideas are the foundation of array traversal and buffer handling in embedded C programming.

  • The three kinds of arithmetic that are allowed on pointers
  • Scaling: how the base type sets the step size
  • Increment, decrement, adding and subtracting integers
  • Checking step sizes with a runnable program
  • Why arithmetic belongs with arrays

Prerequisites

You should know how pointer types determine the size of the object read (previous lecture) and be comfortable with one-dimensional arrays from the earlier chapter of this free embedded C course.

Which Arithmetic Is Allowed on Pointers?

Ordinary numbers support every arithmetic operator, but pointers do not. Multiplying two addresses or dividing one by a number has no meaning, so C forbids it. The valid operations are limited to the following:

OperationExampleResult
Add an integer to a pointerp + 3, p += 3A pointer 3 elements ahead
Subtract an integer from a pointerp - 2, p -= 2A pointer 2 elements behind
Increment or decrementp++, --pMove one element forward or back
Subtract two pointers of the same typeq - pAn integer count of elements between them

Comparing two pointers with ==, < and similar operators is also allowed when both point into the same array. Adding two pointers is not allowed.

Scaling: The Compiler Does the Multiplying

Pointer arithmetic counts in elements, not bytes. When you write p + n, the compiler computes the new address as the old address plus n * sizeof(*p). You never write that multiplication yourself, and that is the whole point: the same source line moves by the right amount for any type.

Pointer typesizeof(*p)Effect of p + 1Effect of p + 3
char *1+1 byte+3 bytes
int *4 on most targets+4 bytes+12 bytes
float *4+4 bytes+12 bytes
double *8+8 bytes+24 bytes

Many older books use a 2-byte int in their examples, so their addresses step by 2. On today’s compilers an int is normally 4 bytes, so we use that here. The idea is identical, only the numbers differ.

int pointer stepping through an array of four ints

byte offset04812
element10203040
pi at starthere–––
after pi++–here––
after pi += 2–––here

Suggested image 3: a row of four array cells with a pointer marker moving by one element at a time. ALT text: “free C tutorial pointer arithmetic scaling by element size”.

Program: Prove the Step Sizes

This original program measures how far p + 1 moves for each type by casting both pointers to char *, which counts raw bytes. It then walks an int pointer forward and backward. Save it as ptr_arith.c.

#include <stdio.h>

int main(void)
{
    char   ca[4] = {'a','b','c','d'};
    int    ia[4] = {10, 20, 30, 40};
    float  fa[4] = {1.5f, 2.5f, 3.5f, 4.5f};
    double da[4] = {0.1, 0.2, 0.3, 0.4};

    char   *pc = ca;
    int    *pi = ia;
    float  *pf = fa;
    double *pd = da;

    printf("char   : p+1 is %td byte(s) ahead\n", (char *)(pc + 1) - (char *)pc);
    printf("int    : p+1 is %td byte(s) ahead\n", (char *)(pi + 1) - (char *)pi);
    printf("float  : p+1 is %td byte(s) ahead\n", (char *)(pf + 1) - (char *)pf);
    printf("double : p+1 is %td byte(s) ahead\n", (char *)(pd + 1) - (char *)pd);

    printf("\nint steps from start (byte offset, value):\n");
    pi++;      printf("pi++     -> %td, %d\n", (char *)pi - (char *)ia, *pi);
    pi += 2;   printf("pi += 2  -> %td, %d\n", (char *)pi - (char *)ia, *pi);
    pi -= 3;   printf("pi -= 3  -> %td, %d\n", (char *)pi - (char *)ia, *pi);
    return 0;
}
$ gcc -Wall -Wextra -std=c11 -o ptr_arith ptr_arith.c
$ ./ptr_arith
char   : p+1 is 1 byte(s) ahead
int    : p+1 is 4 byte(s) ahead
float  : p+1 is 4 byte(s) ahead
double : p+1 is 8 byte(s) ahead

int steps from start (byte offset, value):
pi++     -> 4, 20
pi += 2  -> 12, 40
pi -= 3  -> 0, 10

Follow the second block by hand: starting at offset 0, pi++ goes to offset 4 (value 20). Adding 2 jumps two elements, 8 bytes further, to offset 12 (value 40). Subtracting 3 returns to offset 0 (value 10). The array itself was never modified, only the pointer moved.

Why char Pointers Look Like Ordinary Arithmetic

For a char pointer, the element size is 1, so adding n really adds n bytes. That is why byte-oriented code such as UART buffers and string handling feels like plain integer maths. Switch to a wider type and the scaling quietly appears. This is also why you must never mix up the types: moving a uint32_t * by 1 jumps four bytes, which is wrong if you meant to advance one byte of a packet.

Hand Trace: Predict Before You Run

The best way to master pointer arithmetic is to calculate the result on paper first. Imagine an int array whose first element lives at address 3000, with a 4-byte int. Let p point to it. Work through these steps and compare with what the machine does.

StatementCalculationp afterwards
p++;3000 + 1 * 43004
p = p + 5;3004 + 5 * 43024
p--;3024 – 1 * 43020
p = p - 3;3020 – 3 * 43008

Now repeat the exercise for a double pointer starting at 5000, where each step is 8 bytes, and for a char pointer starting at 7000, where each step is 1 byte. You will notice that the written code is identical in all three cases. Only the declared type changes the arithmetic, and that is exactly what makes C source portable between a 16-bit, 32-bit and 64-bit target when you rely on the compiler instead of hard-coded sizes.

Portable code tip

Never hard-code the number 4 when you mean “the size of an int”. Write sizeof(int) or, better, use pointer arithmetic and let the compiler do the scaling for you.

Pointer Arithmetic in Embedded Systems

  • Buffers: a DMA or UART receive buffer is walked with a pointer that advances after every byte or sample.
  • Register blocks: a peripheral with consecutive 32-bit registers can be reached with base + index on a volatile uint32_t *.
  • Parsers: a protocol parser keeps a pointer and bumps it by the size of each field it consumes.

Common Mistakes

  • Adding a byte count to a typed pointer. pi + 4 moves 16 bytes, not 4. To move by bytes, cast to unsigned char * first.
  • Using a pointer that has stepped out of its array. The result is undefined behaviour, even if you never dereference it more than one past the end.
  • Doing arithmetic on void *. Standard C does not allow it because the element size is unknown, although some compilers accept it as an extension.
  • Subtracting pointers into different arrays. The result is meaningless.
Stay inside the array. The compiler does not check bounds. A pointer may legally point to any element or exactly one position past the last element, and nowhere else.

Best Practices

  • Use pointer arithmetic on arrays and buffers, where the memory layout is guaranteed.
  • Write the loop bound clearly, such as a pointer to one past the end.
  • Prefer clear names like rx_head and rx_end over single letters in firmware.
  • Print offsets with %td, which is the format for pointer differences.

Key Takeaways

  • Only add or subtract integers, increment, decrement and subtract two same-type pointers.
  • Pointer arithmetic counts elements, and the compiler multiplies by sizeof(*p).
  • int, float, double and char pointers step by different byte amounts.
  • Moving a pointer does not change the variable it used to point to.

Conclusion

Scaling is the key idea behind pointer arithmetic. Once you see that p + 1 means “the next element”, array indexing, buffer walking and register access all become natural. In the next lecture of this free embedded C course we separate two look-alike expressions, *(p + 2) and p += 2, and learn how to keep pointers on valid memory.

Frequently Asked Questions

What arithmetic can I do on a pointer in C?

Add or subtract an integer, increment, decrement, and subtract two pointers of the same type.

Why does p + 1 move four bytes for an int pointer?

The compiler scales the integer by sizeof(int), which is usually 4.

Can I add two pointers?

No, the sum of two addresses has no meaning and the compiler rejects it.

How do I move a pointer by exactly one byte?

Cast it to unsigned char * and add 1.

Does incrementing a pointer change the variable?

No. Only the pointer’s stored address changes.

Is pointer arithmetic used in embedded programming?

Yes, constantly, for buffers, register arrays and protocol parsing.

Practise Pointer Arithmetic

Change the arrays in the demo to short and long long, and predict the step sizes before you run it. Then continue with our free C programming course.

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *