Interview
Interview
Interview
Question and
Answer.
Set -3
Linkedin
Owner UttamBasu
Linkedin www.linkedin.com/in/uttam-basu/
Level - Easy
Uttam Basu
1) What happens if you don’t use volatile for a memory-mapped hardware
register?
If you don’t use the volatile keyword for a hardware register (or any variable that
can change outside of program flow), the compiler assumes it doesn't change
unexpectedly. As a result, the compiler may optimize away reads/writes, leading to
incorrect or unexpected behavior.
Example:
Let’s say we’re polling a hardware status register to wait until a flag becomes 1:
If STATUS_REG is not declared as volatile, the compiler might optimize this to:
Because the compiler doesn’t see any code that could change STATUS_REG, it
assumes it never changes — and skips re-reading it from memory. So you might end up
stuck in that loop forever, even if the hardware updates the register.
Correct Usage:
Now the compiler knows it must re-read STATUS_REG on every loop iteration —
preventing this dangerous optimization.
Summary:
● Without volatile: Compiler might cache the value →stale data, bugs, infinite
loops.
● With volatile: Compiler always fetches the actual value →stays in sync with
hardware.
● Applies to:
○ Hardware registers
○ Global variables modified in ISRs
○ Shared memory in multi-threaded systems
Uttam Basu
2) Explain the difference between a stack overflow and a heap overflow.
A. Stack Overflow:
A stack overflow occurs when you use more stack memory than is available,
typically due to:
In Embedded Systems:
Example:
void recursive_function() {
int buffer[1024];
recursive_function(); // infinite recursion
}
This will keep allocating buffer on the stack →eventually the stack overflows.
B. Heap Overflow: A heap overflow happens when a program writes more data to a
dynamically allocated block (via malloc) than it was allocated for.
In Embedded Systems:
Example:
Here, you're writing beyond the bounds of what was allocated — this is a heap
overflow.
Uttam Basu
Key Differences:
3) How does static behave differently for global, local, and function variables
and ISR?
void count_calls() {
static int counter = 0;
counter++;
printf("%d\n", counter);
}
Uttam Basu
C. Static Function:
● Restricts visibility of the function to the file (like private functions in OOP).
void ISR_handler(void) {
static uint8_t toggle = 0;
toggle = !toggle;
}
4) How would you detect a memory leak in an embedded system with no OS and
limited RAM?
Over time, this can exhaust RAM and crash the system.
Wrap malloc() and free() to log allocations, keep count, and track active blocks.
Uttam Basu
typedef struct {
void* ptr;
size_t size;
const char* tag;
} MemTrack;
#define MAX_TRACKED_BLOCKS 50
MemTrack mem_map[MAX_TRACKED_BLOCKS];
Then, add a function to print the memory map or check for unfreed blocks at runtime
or on system exit/reboot.
If you can’t afford full tracking, at least monitor peak heap usage.
● After runtime, check how much of that pattern remains → shows how much
heap was used
Uttam Basu
C. Static Analysis Tools
● PC-lint / Flexelint
● Coverity
● Clang Static Analyzer
● Some IDEs (e.g., IAR, Keil) have built-in leak detection for statically analyzed
memory paths
Summary:
Static analysis tools Finds bugs before they Not always accurate on
happen runtime
Uttam Basu
5) What is memory-mapped I/O, and how does it differ from port-mapped I/O?
Memory-Mapped I/O (MMIO):
In Memory-Mapped I/O, peripheral devices (like GPIO, UART, ADC, etc.) are assigned
specific addresses in the memory address space.
So you access I/O devices the same way you access regular variables or memory —
using pointers.
Everything — RAM, Flash, and I/O — is mapped into a single 32-bit address space.
Example:
Let’s say we want to turn on an LED connected to GPIO Port A, Pin 5. We’d write to a
register like GPIOA->ODR (Output Data Register) — which lives at a fixed address in
the memory space.
Uttam Basu
Code to Turn On the LED:
int main(void) {
// Set PA5 high
GPIOA_ODR |= LED_PIN;
while (1);
}
Most modern embedded toolchains (STM32 HAL, CMSIS, AVR, etc.) give you
predefined macros and structs like this:
Uttam Basu
And GPIO_TypeDef is a struct mapping all GPIO registers:
typedef struct {
volatile uint32_t MODER;
volatile uint32_t OTYPER;
volatile uint32_t OSPEEDR;
volatile uint32_t PUPDR;
volatile uint32_t IDR;
volatile uint32_t ODR; // Output Data Register
// ...
} GPIO_TypeDef;
Feature Benefit
In Port-Mapped I/O, I/O devices have a separate address space, and you need
special instructions (like IN and OUT in x86 assembly) to access them.
Feature Drawback
Uttam Basu
Summary:
6) What are race conditions? How can you prevent them in embedded C?
Race Condition:
● Two or more contexts (e.g., main loop + ISR, or two tasks in RTOS)
● Access shared data (read or write)
● And at least one of them writes
● Without proper synchronization
Result:
Uttam Basu
Example of a Race Condition (Main vs. ISR):
void ISR_Button_Handler(void) {
button_press_count++; // Could be interrupted mid-increment!
}
int main() {
if (button_press_count > 0) {
button_press_count--; // Could corrupt the value
// process button
}
}
This is not safe — ++ and -- are not atomic for multi-byte or even some single-byte
values depending on CPU architecture.
Use this carefully to minimize the disabled time so other ISRs aren't blocked too long.
On some platforms (e.g., ARM Cortex-M), you can use atomic bit manipulation
instructions or functions.
If available:
#include <stdatomic.h>
atomic_uint button_press_count;
On many MCUs, you can also emulate atomic access with LDREX/STREX instructions
or use CMSIS atomic functions.
Uttam Basu
C. Use Volatile Correctly
But note: volatile does not make access safe — it just prevents the compiler from
optimizing it out.
xSemaphoreTake(mutex, portMAX_DELAY);
shared_var++;
xSemaphoreGive(mutex);
Summary:
Uttam Basu
Tips:
// Increment in ISR
void ISR_Handler(void) {
button_press_count++;
}
__disable_irq();
if (button_press_count > 0) {
button_press_count--;
__enable_irq();
// process button
} else {
__enable_irq();
}
This ensures atomicity, avoids race conditions, and keeps ISRs fast (which is key).
When you're working on embedded systems, memory layout is not optional — it's
crucial. You need to tell your program exactly where to put things like:
● Code (.text)
● Initialized data (.data)
● Uninitialized data (.bss)
● Stack, heap, interrupt vector table, etc.
Definition:
A linker script is a configuration file used by the linker (usually ld) to control how your
program’s sections are mapped to physical addresses in memory.
Uttam Basu
Why It’s Important in Embedded C
So, we must manually control the memory layout — and that’s what linker scripts do.
SECTIONS
{
.text : {
KEEP(*(.isr_vector)) /* Interrupt vector table */
*(.text*) /* Application code */
*(.rodata*) /* Read-only data */
} > FLASH
.data : {
_sdata = .;
*(.data*)
_edata = .;
} > RAM AT > FLASH
.bss : {
_sbss = .;
*(.bss*)
*(COMMON)
_ebss = .;
} > RAM
}
Uttam Basu
What It Controls:
Section Meaning
Real-World Uses:
Summary:
Feature Purpose
Uttam Basu