Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Illegal Pointer Operations in C
See which pointer operations the compiler rejects, with real error messages and the correct midpoint formula. Free C programming course for embedded systems.
What You Will Learn in This Lecture
- Which arithmetic operators are legal on pointers and which the compiler rejects
- Why
arr / 2,q * 2andq + qdo not compile, and what the error messages say - How to compute a midpoint between two pointers the correct way
- What pointer subtraction and comparison are allowed to do
- How to cast a pointer to an integer type when you really need the address value in embedded C programming
Prerequisites
This lecture builds on pointer arithmetic and pointer loops from the earlier lectures of this free C programming course. You should already know that adding an integer to a pointer scales by the element size.
Why Illegal Pointer Operations Are Blocked
A pointer is an address with a type attached, not a plain number. Multiplying an address by two or dividing it by two produces a value that points to no object and has no meaning in the language. The compiler refuses these operations to stop you from building addresses that could never be valid. Valid pointer arithmetic is deliberately small: add or subtract an integer, subtract two pointers, compare two pointers.
| Operation | Example | Legal? | Meaning |
|---|---|---|---|
| pointer + integer | p + 3 | Yes | Address of the element 3 positions ahead |
| pointer – integer | p - 2 | Yes | Address of the element 2 positions behind |
| pointer – pointer | hi - lo | Yes (same array) | Number of elements between them |
| pointer compare | lo < hi | Yes (same array) | Which comes earlier in the array |
| pointer + pointer | q + q | No | Adding two addresses has no meaning |
| pointer * integer | q * 2 | No | Scaling an address has no meaning |
| pointer / integer | arr / 2 | No | Halving an address has no meaning |
Legal versus illegal
What the Compiler Says
Try to compile this program. It is a deliberate error; do not expect it to run.
int main(void)
{
int arr[8] = { 0 };
int *q = arr / 2;
int *p = q * 2;
int *r = q + q;
return 0;
}GCC rejects all three lines:
error: invalid operands to binary / (have 'int *' and 'int')
4 | int *q = arr / 2;
error: invalid operands to binary * (have 'int *' and 'int')
5 | int *p = q * 2;
error: invalid operands to binary + (have 'int *' and 'int *')
6 | int *r = q + q;Note the wording: “invalid operands to binary”. The compiler lists the operand types, which tells you what it saw. In the first case the array name arr has already decayed to int *, so the message shows int * rather than int[8].
Good to know: These are hard errors, not warnings, so they never slip into a build. The dangerous mistakes are the ones that **do** compile, such as a wrong cast or an out-of-range subscript.
The Right Way to Find a Midpoint
A common reason people reach for (lo + hi) / 2 is a binary search. With pointers you must not add them. Subtract first to get an element count, halve the count, and add it back to the lower pointer.
#include <stdio.h>
int main(void)
{
int arr[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
int *lo = &arr[1];
int *hi = &arr[6];
printf("hi - lo = %td\n", hi - lo);
printf("lo < hi : %d\n", lo < hi);
printf("middle : %d\n", *(lo + (hi - lo) / 2));
return 0;
}gcc -std=c17 -Wall -Wextra -o ptr_mid ptr_mid.c
./ptr_midExpected output:
hi - lo = 5
lo < hi : 1
middle : 4Computing the midpoint without adding pointers
The same formula, lo + (hi - lo) / 2, also avoids the integer overflow bug that affects (lo + hi) / 2 when lo and hi are plain integer indexes close to the maximum value. It is the form used in well-reviewed library code.
Pointer Comparison and Pointer Difference Rules
- Relational comparisons (
<,>,<=,>=) are only well defined for pointers into the same array object, or one past its end. Equality (==,!=) works between any compatible pointers. - The result of subtracting pointers has type
ptrdiff_t, a signed integer type. Print it with%td. - Pointer difference counts **elements**. For the byte distance, cast both to
char *first. - A pointer to
voidhas no element size, so standard C does not allow arithmetic on it. Some compilers accept it as an extension; do not rely on it.
When You Really Need the Address as a Number
Embedded C programming sometimes needs the numeric value of an address, for example to check alignment or to program a DMA descriptor. Use the integer type designed for it, uintptr_t from <stdint.h>, rather than int or long.
#include <stdint.h>
uintptr_t addr = (uintptr_t)p;
int aligned = (addr % 4u) == 0;Arithmetic is then done on the integer, not the pointer. The reverse conversion, (int *)addr, is allowed but only meaningful if the number really is a valid address for that type, which is exactly the situation with memory-mapped registers.
Warning: Do not store a pointer in an int on a 64-bit target. An int is only 32 bits, so the upper half of the address is lost and the pointer you convert back is wrong.
Worked Example: Reasoning About an Expression Before You Compile
When you meet an unfamiliar pointer expression, check three things in order. First, what is the type of each operand: array, pointer, integer? Second, is the operator allowed for that combination of types? Third, if it is allowed, what is the scale factor, meaning how many bytes does one step cover? Apply this to hi - lo in the program above. Both operands are int *, subtraction of two pointers into the same array is allowed, and the result is an element count, so the answer is 5 with no byte arithmetic involved. Apply it to q + q: two pointers, addition, not in the allowed list, so the compiler stops you. This habit catches most mistakes before you ever press the build button, and it is exactly the skill that output-prediction questions in a free C tutorial or a firmware interview are testing.
A second practical check concerns the difference between a pointer and the number it holds. You can print the address with %p after casting to void *, but you cannot reason about it with ordinary arithmetic. If you find yourself wanting to mask or round an address, for example to align a DMA buffer, that is the moment to convert to uintptr_t, do the integer work, and convert back at the last possible line.
Common Mistakes and Troubleshooting
| Mistake | Symptom | Fix |
|---|---|---|
(lo + hi) / 2 on pointers | Compile error: invalid operands | Use lo + (hi - lo) / 2 |
Printing hi - lo with %d | Warning or wrong output on 64-bit | Use %td |
Comparing pointers into different arrays with < | Unspecified result | Compare only within one array |
Converting a pointer to int | Truncated address on 64-bit | Use uintptr_t |
Arithmetic on void * | Rejected or non-portable | Cast to char * or uint8_t * first |
Best Practices, Performance and Security
- Keep pointer arithmetic simple: add or subtract an integer, subtract two pointers, compare. Anything else deserves a comment or a redesign.
- Do pointer-to-integer conversions in one small helper so the intent, and the portability risk, is in a single place.
- On a 32-bit microcontroller,
uintptr_tis 32 bits and the code works unchanged; on 64-bit hosts it is 64 bits. This is why it is the right type for portable test builds. - Security: integer checks on pointer values such as “is this address inside the buffer?” should compare against the buffer bounds, never against attacker-supplied lengths alone.
- Treat compiler errors about pointer operands as helpful: they are the language telling you the expression has no meaning.
Summary and Key Takeaways
- Legal: pointer plus or minus integer, pointer minus pointer, pointer comparison.
- Illegal: pointer plus pointer, pointer times, divided by, or modulo an integer.
- Midpoint is
lo + (hi - lo) / 2. - Pointer difference counts elements and has type
ptrdiff_t. - Use
uintptr_twhen you need an address as an integer.
Conclusion
Knowing what a pointer cannot do is as valuable as knowing what it can. The compiler errors in this lecture are your friends, because they stop you before the program runs. In the final lecture of this pointers chapter in our free embedded C course, we combine pointers to pointers with functions that modify the caller’s pointer, and finish with a quiz and interview questions.
Frequently Asked Questions
Why can I not add two pointers in C?
The sum of two addresses has no meaning. C only allows adding an integer to a pointer, which moves it by whole elements.
How do I find the middle of two pointers?
Use lo + (hi - lo) / 2. Subtract to get an element count, halve it and add the result to the lower pointer.
What type does pointer subtraction return?
ptrdiff_t, a signed integer type from <stddef.h>. Print it with the %td format specifier.
Can I multiply or divide a pointer?
No. The compiler rejects *, / and % on pointer operands. Convert to uintptr_t first if you genuinely need numeric arithmetic on an address.
Is comparing two pointers always allowed?
Equality comparison is. Relational comparison is only well defined when both pointers refer to the same array or one past its end.
Why use uintptr_t instead of unsigned long?
uintptr_t is defined to hold any object pointer, so it is the right width on every platform, while unsigned long can be too small, for example on 64-bit Windows.
Almost Done With Pointers
The last lecture in this chapter covers pointer to pointer, plus a quiz and interview questions.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments