Hello! Welcome to the first lesson of your Embedded Interview Preparation Bootcamp.
Given your extensive experience in automotive embedded software, you're already familiar with the power and pitfalls of pointers in C. This first module is designed to sharpen that knowledge, focusing on the deep, low-level details that distinguish an expert and are frequently probed in interviews at top semiconductor firms.
Today, we'll start with a fundamental but surprisingly nuanced topic: pointer arithmetic. Our goal is to go beyond the basics and rigorously compare the behavior of arithmetic across typed, void, and function pointers, with a special focus on what is guaranteed by the C standard versus what constitutes dangerous undefined behavior.
1. Pointer Arithmetic with Typed Pointers: The Standard Rules
You use typed pointer arithmetic every day when you iterate through arrays. When you add an integer n to a pointer p of type T*, the compiler calculates the new address as address_of_p + n * sizeof(T). This scaling is what makes arr[i] work seamlessly.
However, interviews often test the edge cases. The C standard is very specific about when this arithmetic is valid.
Subtleties with Pointer Arithmetic in C
To understand the formal rules, let's read a short but crucial article that references the C standard. This will clarify the exact boundaries of well-defined pointer behavior.
Please read the first section, titled 'Object Bounds'. Pay close attention to the three summary bullet points that define what makes a pointer valid or undefined.
As the article explains, pointer arithmetic is only guaranteed to be well-defined if the pointer operand and the result point to elements of the same array object, or one past the last element.
Let's break this down with a practical code example:
#include <stdio.h>
int main(void) {
int arr[5] = {10, 20, 30, 40, 50};
int *p_start = &arr[0];
int *p_end = &arr[4];
// Well-defined: Pointer points one past the end of the array.
// This is valid for loop comparisons, e.g., for(p = arr; p < arr + 5; p++).
int *p_one_past = arr + 5;
printf("Address of arr[0]: %p\n", (void*)p_start);
printf("Address 'one past': %p\n", (void*)p_one_past);
// Undefined Behavior (UB)!
// The C standard does not guarantee what happens when you form an
// address that is before the beginning of an object.
int *p_before = arr - 1;
printf("Address 'one before' (UB): %p\n", (void*)p_before);
// On many systems, this might "work" by simply subtracting sizeof(int),
// but the compiler is free to do anything. It could crash, or worse,
// appear to work until an optimization flag is changed.
return 0;
}
Key Interview Takeaway: Knowing that pointing before an object is undefined behavior, while pointing one element past is well-defined, is a critical distinction. An interviewer might present code like the p_before example and ask you to identify the bug.
2. void* Pointer Arithmetic: Standard vs. Extension
A void* is a generic pointer to data, but it carries no type information. This lack of type information has a profound impact on pointer arithmetic.
To see this in action, please watch the following video. It demonstrates the problem and the correct, portable solution.
What about Pointer Arithmetic with Void Pointers?
This video from Jacob Sorber clearly explains the issue with void pointer arithmetic and the distinction between standard C behavior and a common compiler extension.
Watch from the beginning up to the 5:34 mark. The video covers: A quick review of typed pointer arithmetic (0:50). The attempt to perform arithmetic on a void* (2:20). The explanation of why this is undefined behavior in the C standard (2:45). The correct, portable way to perform byte-wise arithmetic using casts (4:16).
As the video explains:
- Standard C: Performing arithmetic directly on a
void*is undefined behavior. The compiler doesn't know the size of the underlying type (sincesizeof(void)is meaningless), so it doesn't know what to scale the increment by. - GNU C Extension: The GCC compiler, widely used in embedded systems, does define this behavior as an extension. It treats
sizeof(void)as1. This meansvoid_ptr++will increment the address by a single byte.

The Professional Approach:
Never rely on compiler extensions for this. For portable, byte-addressable arithmetic, you must always cast the void* to a pointer to a character type, as their size is guaranteed to be 1.
#include <stdio.h>
#include <stdint.h> // For uint8_t
void* my_memcpy(void* dest, const void* src, size_t n) {
// Cast void pointers to byte-pointers for arithmetic
uint8_t* d = dest;
const uint8_t* s = src;
for (size_t i = 0; i < n; i++) {
d[i] = s[i]; // Correctly copies byte-by-byte
}
return dest;
}
In this my_memcpy implementation, casting to uint8_t* ensures that pointer increments and array-style access (d[i]) operate on single bytes, which is exactly what's needed. This code is portable and well-defined.
3. Function Pointer Arithmetic: The Forbidden Zone
Function pointers are fundamentally different from data pointers. They point to executable code in the .text section, not to data objects in .data, .bss, or the heap.
So, can you perform arithmetic on them? Let's quickly review what function pointers are and then see what happens when we try to apply arithmetic.
Function Pointers | C Programming Tutorial
This next video gives a good overview of function pointers. We'll watch a specific section that directly addresses arithmetic.
Please watch the segment from 15:27 to 18:16. The presenter explicitly discusses and demonstrates why pointer arithmetic is not applicable to function pointers and what happens when you try it.
The conclusion is unambiguous: you cannot perform arithmetic on function pointers. It is undefined behavior.
The concepts of "size" and "next element" don't apply to functions in memory in the same way they do for an array of data. While the compiler might place functions sequentially in the binary, there are no guarantees. Incrementing a function pointer is meaningless and will almost certainly lead to a crash when you try to call the resulting invalid address.
A Critical Distinction: Pointer to a Function vs. Array of Pointers to Functions
Don't confuse performing arithmetic on a function pointer with performing arithmetic on a pointer to an array of function pointers. The latter is perfectly valid.
void state_A(void) { /* ... */ }
void state_B(void) { /* ... */ }
void state_C(void) { /* ... */ }
// An array of function pointers. This is an array of DATA.
void (*state_table[])(void) = { state_A, state_B, state_C };
// 'p' is a pointer to the first element of the array.
// The element itself is a function pointer.
void (**p)(void) = state_table;
// This is VALID. We are incrementing 'p', which is a pointer to data.
// It now points to the second element of the array, state_B.
p++;
(*p)(); // Calls state_B.
This pattern is the basis for jump tables and state machines, which we will implement in the next lesson.
Summary and Key Interview Questions
Let's consolidate what we've learned.
| Pointer Type | Arithmetic p + 1 Behavior |
C Standard Compliance |
|---|---|---|
Typed Pointer (T*) |
Increments address by sizeof(T). |
Well-defined (within object bounds). |
Void Pointer (void*) |
sizeof(void) is unknown. |
Undefined Behavior. |
void* (GNU Extension) |
Increments address by 1 byte. |
Non-standard, but a common extension. |
Function Pointer (R (*)(A)) |
Size/concept of "next" is meaningless. | Undefined Behavior. |
To test your understanding, consider how you would answer these common interview questions:
- Question: "What is the result of
ptr + 1ifptris anint*, avoid*, and a function pointervoid (*ptr)(void)? Explain your reasoning for each in the context of the C standard." - Question: "In a code review, you see
void_ptr++. The code builds with GCC and seems to run correctly. What feedback would you provide to the author?" - Question: "Given
int arr[10];, is the expressionarr + 10that forms a pointer to one element past the array valid? What aboutarr - 1? Why?" - Question: "How would you implement a function to zero out a 1KB memory region given a
void*to its start, ensuring your code is portable across different compilers?"
Conclusion
In this lesson, we dissected the rules of pointer arithmetic, emphasizing the critical line between well-defined behavior and undefined behavior. For a senior embedded engineer, mastering these details is non-negotiable—it's the foundation for writing safe, portable, and efficient low-level code.
You've learned:
- Typed pointer arithmetic is scaled by the size of the type and is only defined within the bounds of an object (and one element past).
- Standard C forbids arithmetic on
void*, but the common GNU C extension permits it by assuming a size of 1. The portable solution is to always cast tochar*oruint8_t*. - Function pointer arithmetic is always undefined behavior because function pointers refer to code, not data objects with a defined size.
In our next lesson, we will put this knowledge to practical use by implementing a state machine using a function pointer array (jump table). This will build directly on our discussion of arrays of function pointers and demonstrate a powerful, efficient pattern used extensively in embedded systems.