pointer arithmetic
Once you can point at one element of an array, it would be handy to point at the next one. Pointer arithmetic is exactly that: adding an integer to a pointer to move it along by that many elements, or subtracting two pointers to ask 'how many elements apart are they?'. It is how you walk through memory.
The key rule is that pointer arithmetic scales by the size of the pointee, not by bytes. If p is an int * (and int is 4 bytes), then p + 1 is 4 bytes further on — it lands on the next int, not the next byte. p + 3 advances 3 * 4 = 12 bytes. This is why p + 1 always means 'next element' for any type: the compiler multiplies the integer by sizeof(pointee) for you. Subtraction works the same way: q - p gives the number of elements between them (a count of type ptrdiff_t), so for two int pointers 12 bytes apart, q - p is 3. You may legally compute addresses within an array, and one element past its end (as a stop marker you do not dereference), but stepping further than that, or doing arithmetic on pointers into two unrelated objects, is undefined behaviour.
Pointer arithmetic is the engine under array traversal and string scanning. Walking *p++ down a char * to find the terminating '\0' of a string, or sweeping a pointer from the start to the end of a buffer, is pointer arithmetic in action — fast, but utterly unforgiving if you step outside the object you were given.
int a[3] = {10, 20, 30}; int *p = a; — then *(p + 2) is 30, and (p + 2) - p is 2 (two ints apart, even though that is 8 bytes of address). p + 1 advanced sizeof(int) = 4 bytes, not 1.
+1 means one element; subtraction counts elements, not bytes.
Arithmetic is only defined inside one array object (and one past its end). Running a pointer beyond the array, or comparing pointers into separate allocations, is undefined behaviour even if the raw addresses happen to look adjacent — a very common source of off-by-one and buffer-overrun bugs.