✓ Embedded C — Interview Prep

Embedded C Interview Questions

Does preprocessing happen in Assembly Language code?

It depends on the file extension used:

  • .s (lowercase) — Compiler will not perform preprocessing on the assembly code.
  • .S (uppercase) — Tells the compiler to perform preprocessing on the assembly code as well.

Note: armclang is mostly used as a command-line tool to compile assembly code for ARM processors/microcontrollers.

Macros for Data Manipulation in C
Bit ManipulationMacros
// Swap Nibbles in a Byte
#define SwapNibbles(data) (((data & 0x0F)<<4) | ((data & 0xF0)>>4))

// Swap Two Bytes
#define SwapTwoBytes(data) (((data & 0x00FF)<<8) | ((data & 0xFF00)>>8))

// Swap Two Numbers
#define SWAP(x,y) (x^=y^=x^=y)

// Convert One Endian into Another (32-bit)
#define SwapEndians(value) \
  ((value & 0x000000FF) << 24) | ((value & 0x0000FF00) << 8) | \
  ((value & 0x00FF0000) >> 8)  | ((value & 0xFF000000) >> 24)

// Get LOW and HIGH Bytes from a Word
#define LOW_BYTE(x)  ((unsigned char)(x) & 0xFF)
#define HIGH_BYTE(x) ((unsigned char)(x >> 8) & 0xFF)
What is Startup Code?

Definition: A small block of Assembly Language code that prepares the way for execution of software written in a High Level language.

Actions performed by Startup Code:

  • Disable all interrupts
  • Copy initialized data from ROM to RAM
  • Zero-initialize the uninitialized data area (BSS segment)
  • Enable interrupts
  • Initialize the processor's Stack Pointer
  • Initialize the Heap
  • Execute constructors and initializers for all global variables (C++ only)
  • Call main() — the entry point to the program
What is Buffer Overflow in C?

Buffer: A region of computer memory with defined boundaries referenced by a program variable.

Buffer Overflow: Occurs when data written into a memory buffer goes past its left or right boundary.

char buff[10];
buff[10] = 'a';  // ❌ Buffer overflow — valid indices are 0 to 9

Why it's harmful:

  • Can cause programs to crash or produce unexpected results
  • Attackers can exploit it to crash programs, corrupt data, steal information, or execute arbitrary code
What are Callback Functions in C?

Callback: Executable code passed as an argument to another function, expected to be called back on the occurrence of some event (e.g., an interrupt).

In C, a callback is implemented using a function pointer. It has 3 steps: the callback function, its registration, and its execution.

#include <stdio.h>

void PrintMsg(void);
void RunCallBack(void(*fptr)(void));

int main(void) {
  RunCallBack(&PrintMsg);  // Pass address of PrintMsg
  return 0;
}

void PrintMsg() {
  printf("Hello World");
}

void RunCallBack(void(*fptr)(void)) {
  (*fptr)();  // Execute callback
}
// OUTPUT: Hello World

Advantages: The calling function can pass any parameters it wishes; it allows correct information hiding.

Pass Individual Structure Members to a Function (Call by Value)
StructuresFunctions
#include <stdio.h>

struct student {
  char name[50];
  int age;
  int roll_no;
  float marks;
};

void Print(char name[], int, int, float);

int main() {
  struct student s1 = {"Abhishek", 18, 50, 72.5};
  Print(s1.name, s1.age, s1.roll_no, s1.marks);
  return 0;
}

void Print(char name[], int age, int Roll_no, float marks) {
  printf("%s %d %d %.2f", name, age, Roll_no, marks);
}
// OUTPUT: Abhishek 18 50 72.50
Passing Structure to a Function by Reference
StructuresPointers
#include <stdio.h>

struct Employee {
  char name[20];
  int age;
  char Doj[10];       // Date of Joining
  char designation[20];
};

void Print(struct Employee *);

int main() {
  struct Employee data = {"Abhishek Mane", 27, "2/5/2018", "Systems Engineer"};
  Print(&data);
  return 0;
}

void Print(struct Employee *ptr) {
  printf("Name:%s\n",        ptr->name);
  printf("Age:%d\n",         ptr->age);
  printf("Date of joining:%s\n", ptr->Doj);
  printf("Designation:%s\n", ptr->designation);
}
// OUTPUT:
// Name:Abhishek Mane
// Age:27
// Date of joining:2/5/2018
// Designation:Systems Engineer
Difference between Memory Mapped I/O and I/O Mapped I/O
FeatureMemory Mapped I/OI/O Mapped I/O
Address SpaceI/O and Memory share the same address spaceI/O and Memory have separate address spaces
Control SignalsMEMR and MEMWIOR and IOW
Address Lines16-bit (A0 to A15)8-bit (A0 to A7)
MemoryShared in 64KB Memory256 I/P lines and 256 O/P lines
Data TransferBetween any register and I/O deviceOnly between Accumulator and I/O device
HardwareMore hardware needed to decode 16-bit addressLess hardware needed to decode 8-bit address
InstructionsLDA, STAIN and OUT instructions
What is a Timer in Embedded Systems? (Method 1 — Polling Timer Flag)

Timer: A free-running binary counter that increments for each clock pulse applied to it. Counts from 0 to (2ⁿ − 1) where n = number of timer bits.

  • 8-bit Timer: 0 to 255
  • 16-bit Timer: 0 to 65535
  • PIC takes 4 clock cycles per instruction → Fosc/4

Timer Tick Calculation Example (50ms delay):

Fosc = 4MHz, Prescalar = 1
Timer Tick = Prescalar / (Fosc/4) = 4/4MHz = 1us
Timer Count = 50ms / 1us = 50000
Load Value = 65536 - 50000 = 15536 (0x3CB0)
// PIC18F4520 — 50ms delay using Timer1
void delay_50ms(void) {
  T1CON   = 0x08;       // No Prescaler
  TMR1H   = 0x3C;
  TMR1L   = 0xB0;
  T1CONbits.TMR1ON = 1; // Start Timer
  while(PIR1bits.TMR1IF == 0); // Poll flag
  T1CONbits.TMR1ON = 0; // Stop Timer
  PIR1bits.TMR1IF  = 0; // Clear flag
}
Timer in Embedded Systems — Method 2: Counting Timer Overflows

In this method a variable count is incremented each time the timer overflows. When count reaches a pre-calculated value, an action is taken (e.g., toggle LED).

Fosc = 20MHz, Fcpu = 5MHz, Prescalar = 2
TimerTick = 2 / (20MHz/4) = 0.4us
Time per 8-bit Overflow = 256 × 0.4us = 102.4us
OverflowCount = 1s / 102.4us = 9766 overflows
// 1-second delay by counting 9766 overflows
unsigned int count = 0;
TCON  = 0x40;  // 8-bit Timer, Prescalar:2
while(1) {
  T0CONbits.TMR0ON = 1;          // Start Timer0
  while(INTCONbits.TMR0IF == 0); // Wait for overflow
  INTCONbits.TMR0IF = 0;         // Clear flag
  if(count != 9766) {
    count++;
  } else {
    count = 0;
    LED = ~LED;                  // Toggle LED
    T0CONbits.TMR0ON = 0;        // Stop Timer
  }
}
C Function to Transmit Decimal Value via USART Without Library

Splits an integer into individual digits (Ten-Thousands, Thousands, Hundreds, Tens, Units) and transmits each digit as ASCII over USART.

void Transmitt_Decimalvalue(unsigned int Data) {
  unsigned char d1,d2,d3,d4,d5;
  d1 = Data / 10000U;
  d2 = (Data % 10000U) / 1000U;
  d3 = (Data % 1000U)  / 100U;
  d4 = (Data % 100U)   / 10U;
  d5 = Data % 10U;

  if(Data >= 10000) {
    TXREG = d1 + 0x30;  while(PIR1bits.TXIF==0);
    TXREG = d2 + 0x30;  while(PIR1bits.TXIF==0);
    TXREG = d3 + 0x30;  while(PIR1bits.TXIF==0);
    TXREG = d4 + 0x30;  while(PIR1bits.TXIF==0);
    TXREG = d5 + 0x30;  while(PIR1bits.TXIF==0);
  } else if(Data >= 1000) {
    TXREG = d2 + 0x30;  while(PIR1bits.TXIF==0);
    // ... and so on
  }
}
Count Number of Set Bits in an Integer (Brian Kernighan's Method)
Bit ManipulationImportant

Each iteration of n &= (n-1) clears the lowest set bit. Count how many times until n becomes 0.

#include <stdio.h>

unsigned int CountSetBits(unsigned int n) {
  unsigned int count = 0;
  while(n != 0) {
    n &= (n - 1);  // Clear lowest set bit
    count++;
  }
  return count;
}

int main() {
  unsigned int number = 15;  // 1111 in binary
  printf("SetBits:%d", CountSetBits(number));
  return 0;
}
// OUTPUT: SetBits:4
SPI Protocol — Clock Polarity (CPOL) and Clock Phase (CPHA)

Clock Polarity (CPOL): Determines the IDLE state of the clock.

  • CPOL = 0 → IDLE state of clock is LOW
  • CPOL = 1 → IDLE state of clock is HIGH

Clock Phase (CPHA): Determines the edge (Rising/Falling) at which data Read/Write occurs.

  • CPHA = 0 → Data sampled on the leading edge
  • CPHA = 1 → Data sampled on the trailing edge

The 4 combinations of CPOL and CPHA give SPI Modes 0, 1, 2, 3.

What are Setter and Getter Functions in Embedded C?

Setter: Sets or updates a variable's value.   Getter: Retrieves a variable's value.

Use case: PWM application — Setter sets duty cycle, Getter reads it.

#include <stdio.h>

static int x;

void Setvalue(int value) { x = value; }  // Setter
int  Getvalue(void)      { return x; }   // Getter

int main() {
  Setvalue(10);
  printf("x=%d", Getvalue());
  return 0;
}
// OUTPUT: x=10
C Program to Copy a String Using Pointers
#include <stdio.h>

void Copystring(char *target, char *source) {
  while(*source != '\0') {
    *target = *source;
    source++;  // Move to next source char
    target++;  // Move to next target location
  }
  *target = '\0';  // Append NULL terminator
}

int main(void) {
  char src[15] = "Helloworld!!";
  char dst[15];
  printf("Sourcestring=%s\n", src);
  Copystring(dst, src);
  printf("Targetstring=%s", dst);
  return 0;
}
// OUTPUT:
// Sourcestring=Helloworld!!
// Targetstring=Helloworld!!
C Function to Display 10-bit ADC Result on 16x2 LCD

Splits a 10-bit ADC value (0–1023) into individual digits and sends them to the LCD data port one by one.

void DisplayADCresult(unsigned int adc_count) {
  unsigned int x = 0;
  unsigned char temp[4];

  temp[0] = ((adc_count / 1000U) + 48);         // Thousands digit
  temp[1] = (((adc_count / 100U) % 10U) + 48);  // Hundreds digit
  temp[2] = (((adc_count / 10U)  % 10U) + 48);  // Tens digit
  temp[3] = ((adc_count % 10U) + 48);            // Ones digit

  for(x = 0; x < 4; x++) {
    lcddata(temp[x]);  // Send to LCD data port
  }
}
C Program to Swap Endians (Convert Little ↔ Big Endian)
EndiannessImportant
#include <stdio.h>
#include <inttypes.h>

uint32_t SwapEndians(uint32_t Value) {
  uint32_t Converted = 0;
  Converted |= (Value & 0xFF000000) >> 24;
  Converted |= (Value & 0x00FF0000) >> 8;
  Converted |= (Value & 0x0000FF00) << 8;
  Converted |= (Value & 0x000000FF) << 24;
  return Converted;
}

int main() {
  uint32_t Data = 0x12345678;
  uint32_t Swapped = SwapEndians(Data);
  printf("SwappedData: 0x%x\n", Swapped);
  return 0;
}
// OUTPUT: SwappedData: 0x78563412
How are Interrupts Handled by a Microcontroller?
  1. MCU finishes the current instruction and saves the Program Counter (PC) address of the next instruction onto the Stack.
  2. It saves the current status of all interrupts internally (not on the stack).
  3. MCU jumps to the Interrupt Vector Table (IVT), which holds addresses of all ISRs.
  4. MCU fetches the ISR address from the IVT and begins executing it until it reaches the RETFIE (Return From Interrupt Exit) instruction.
  5. Upon executing RETFIE, the MCU pops the saved PC from the stack and resumes execution from where it was interrupted.
What are Lvalues and Rvalues in C?

lvalue: An expression that refers to a memory object — something that can appear on the left-hand side of an assignment. (Note: const objects are lvalues but cannot be assigned.)

rvalue: An expression that can appear on the right-hand side but not the left-hand side — typically temporaries or literals.

int n;
n = 5;        // n is lvalue; 5 is rvalue
char buf[3];
buf[0] = 'a'; // buf[0] is lvalue; 'a' is rvalue

int *p = new int;   // p is lvalue; 'new int' is rvalue
f() = 0;            // function returning reference = lvalue
s1.size();          // function call (no ref return) = rvalue

An lvalue can be implicitly converted to an rvalue, but not vice versa.

How is a Signed Negative Number Stored in Memory?

In most Intel architectures, signed negative numbers are stored as Two's Complement.

+5  =  0101
-5  =  1011  (Two's Complement of 5)

// To convert: Invert all bits → Add 1
// 0101 → 1010 → 1010 + 1 = 1011 = -5
Count Down vs Count Up Loops — Which is Better?

Count Down to Zero loops are better for optimization.

At loop termination, comparison to zero can be optimized by the compiler using a single SUBS instruction. With count-up loops, the compiler needs both ADD and CMP instructions at the end of each iteration — costing one extra instruction cycle.

// Preferred: Count Down
for(int i = 10; i > 0; i--) { ... }

// Less optimal: Count Up
for(int i = 0; i < 10; i++) { ... }
What is Loop Unrolling?

Small loops can be unrolled for higher performance. The loop counter is updated less often and fewer branch instructions are executed. Tradeoff: increased code size.

// Normal loop
int countbit1(uint n) {
  int bits = 0;
  while(n != 0) { if(n & 1) bits++; n >>= 1; }
  return bits;
}

// Unrolled — processes 4 bits per iteration
int countbit2(uint n) {
  int bits = 0;
  while(n != 0) {
    if(n & 1) bits++;
    if(n & 2) bits++;
    if(n & 4) bits++;
    if(n & 8) bits++;
    n >>= 4;
  }
  return bits;
}
How does taking the address of a local variable result in unoptimized code?

The most powerful compiler optimization is register allocation — keeping variables in CPU registers instead of memory. Local variables are normally allocated to registers.

However, when you take the address of a local variable with &, the compiler cannot keep it in a register — it must reside in memory so it has an addressable location. This prevents register allocation and results in slower, unoptimized code.

Why do Global Variables Result in Unoptimized Code?

For the same reason as local variables whose addresses are taken — the compiler cannot put global variables into registers. Since any function can potentially access them, the compiler must always read/write them from main memory, making global-heavy code inherently less optimal.

char, short, or int — Which is Best for Local Variable Optimization?

Prefer int / unsigned int for local variables where possible.

For char and short, the compiler must reduce the variable to 8 or 16 bits after every assignment using sign-extension or zero-extension shifts — typically 2 extra instructions per assignment. Using int avoids these shifts entirely, even if the input/output data is 8 or 16 bits.

// Less optimal — compiler adds sign-extension
unsigned char x = a + b;

// More optimal — process as 32-bit, convert only at output
unsigned int x = a + b;
result = (unsigned char)x;
Can Structures be Passed to Functions by Value?

Yes, but it is wasteful. Call by value means the function allocates local stack memory to copy the entire structure — which can be very large.

Best practice: Always pass structures by address (pointer) to avoid unnecessary memory copy and stack overhead.

// Inefficient
void process(struct LargeStruct s) { ... }

// Efficient
void process(struct LargeStruct *s) { ... }
Why Can't Arrays be Passed by Value to Functions?

When an array is passed to a function, it decays to a pointer internally. Pointers are always passed by reference (they hold the address of the first element). There is no copy of the array data.

void func(int arr[]) {
  // arr is actually int *arr here
  // Modifying arr[i] modifies the original array
}
Advantages and Disadvantages of Macros vs Inline Functions

Advantage: Both are more efficient than a normal function call — no call/return overhead since they are inlined directly into the code.

Disadvantage: Both increase the size of the executable code.

Key differences:

AspectMacroInline Function
Expanded byPreprocessorCompiler
Argument evaluationMultiple times (side effects possible)Once (safe)
Type checkingNoneYes
DebuggingHarderEasier
What Happens When Recursive Functions are Declared Inline?

An inline function is expected to be fully copied at each call site. A recursive inline function would theoretically require infinite copies — creating a huge burden on the compiler.

In practice, the compiler typically ignores the inline hint for recursive functions. If it did attempt to inline them, the stack may overflow, especially for large function bodies or deep recursion.

What is the Scope of a Static Variable?

A static local variable retains its value throughout the entire life of the program — it is initialized only once and persists across function calls.

void counter() {
  static int count = 0;  // Initialized only once
  count++;
  printf("Count: %d\n", count);
}
// Each call increments count: 1, 2, 3, ...

A static global variable or function has file scope — it is not visible outside the translation unit in which it is declared.

Which Infinite Loop Form is Most Efficient?

All three forms produce the same result but for(;;) is considered the most idiomatic in embedded C:

// Form 1
while(1) { statements; }

// Form 2
do { statements; } while(1);

// Form 3 — Preferred in embedded
for(;;) { statements; }

for(;;) clearly signals intent and some compilers may generate slightly cleaner code since no condition needs to be evaluated.

What is Interrupt Latency?

Definition: The time between the generation of an interrupt by a device and the actual servicing of that interrupt (i.e., start of ISR execution).

How to reduce interrupt latency:

  • Keep ISRs short and fast — do minimal work inside the ISR
  • Avoid disabling interrupts for long periods
  • Use hardware priority levels for critical interrupts
  • Minimize nested interrupt depth
What are the Different Storage Classes in C?
ClassStorageLifetimeScope
autoStack (default)Function scopeLocal
registerCPU Register (hint)Function scopeLocal
staticMain memory (data segment)Program lifetimeLocal or file
externMain memoryProgram lifetimeGlobal — across files

Note: register and static differ only in storage location. extern can be accessed from any file in the project.

What are Type Qualifiers in C? (const, volatile, restrict)

const: No writes allowed through this lvalue. Object value cannot be modified after initialization.

const int x = 10;  // x cannot be changed

volatile: No caching — every access must read/write the actual memory location. Used for hardware registers or variables modified by ISRs.

volatile uint32_t *GPIOA_IDR = (uint32_t*)0x40020010;
// Compiler will NOT optimize reads away

restrict: A pointer-only qualifier. Tells the compiler that the object it points to is not aliased by any other pointer — enabling aggressive optimization.

void copy(int * restrict dst, const int * restrict src, int n);
How to Solve the Dangling Pointer Problem?

Two approaches:

1. Declare local variables as static if returning their address, so they persist beyond the function scope.

2. Set the pointer to NULL after freeing — a NULL pointer is safe to check and won't point to a stale memory location.

int *ptr = (int *)malloc(5 * sizeof(int));
free(ptr);   // ptr is now a dangling pointer ❌
ptr = NULL;  // Now ptr is a safe NULL pointer ✓

// Always check before use
if(ptr != NULL) { *ptr = 10; }
Important Programming Questions on Arrays
ArraysInterview Favorites
  • Find the missing number in an integer array of 1 to 100
  • Find the duplicate number in a given integer array
  • Find all pairs whose sum equals a given number
  • Find the largest and smallest number in an unsorted array
  • Find duplicate numbers if array contains multiple duplicates
  • Remove duplicates from an array in-place
  • Find multiple missing numbers in an array with duplicates
  • Rotate an array Left and Right by K positions
  • Find start and end position of a given value in a sorted array
Tricky C Interview Questions List
TrickyMust Know
  • What does malloc(0) return?
  • Write a function that takes a byte + field, returns value of that field
  • Write a macro for number of seconds in a year (ignoring leap years)
  • Write a standard MIN(a,b) macro
  • What is the problem with gets() and how to avoid it?
  • How does free() know the size of memory to deallocate?
  • How to call a function before main()?
  • Create a function accepting different data types and returning int
  • What are the various reasons for a Segmentation Fault in C?
  • How to override an existing macro in C?
  • How to print a string containing % in printf?
Important Embedded System Interview Questions List
Must Prepare
  • What is the significance of Watchdog Timer?
  • Explain Read-Modify-Write technique
  • What is Interrupt Latency and how to reduce it?
  • What are different activities performed by Startup Code?
  • Explain the Boot process of a Microcontroller
  • What is the function of a DMA Controller?
  • How is a program executed — Bit by Bit or Byte by Byte?
  • What is the role of a Bootloader in Embedded Systems?
  • Difference between Bit Rate and Baud Rate
  • What are the different customizations of the volatile keyword?
  • What is Virtual Memory?
  • What is a .lst file? What is EQU?
Commonly Asked Technical C Programs in Interviews
Practical
  • Add function — handle data type overflow (uint8_t x = 255 + 255 overflows)
  • Factorial function — iterative and recursive
  • Matrix operations — multiplication, addition
  • Sine function without math.h
  • Searching and sorting algorithms
  • Knowledge of UART, CAN, ADC, SPI, Timers, Interrupts
// Overflow example
uint8_t x = 255, y = 255, z;
z = x + y;  // ❌ z = 254 (overflow: 510 > 255)

// Safe approach — use wider type for intermediate
uint16_t temp = (uint16_t)x + y;
if(temp > 255) { /* handle overflow */ }
Important and Rarely Asked C Interview Questions
  • What happens when we call a function in C?
  • How can we return multiple values from a function?
  • What happens if Stack and Heap regions overlap?
  • How does free() know the exact size of memory to deallocate?
  • Explain Segmentation Fault in C
  • Why can String Literals not be modified?
  • How to call a function before main()?
  • How to override an existing MACRO in C?
  • What are Memory Leaks and how to prevent them?
  • What is NULL Pointer assignment error vs NULL Pointer Exception?
  • Why can't Pointer Arithmetic be performed on Void Pointers?
  • How is Dynamic Linking different from Static Linking?
🔒 Premium Course

Unlock 400+ Interview
Questions

Deep-dive coverage of C programming, Operating Systems, Linux System Programming, Kernel Development, RTOS, and Advanced ARM — all in one place.

Part 1: C & Data Structures50 questions — Pointers, memory, algorithms
Part 2: Operating Systems50 questions — Scheduling, deadlocks, sync
Part 3: Linux System Programming50 questions — Processes, IPC, sockets
Part 4: Embedded & ARM50+ questions — Cortex, power, optimization
Part 5: Kernel & Device Drivers50 questions — LKM, sync, interrupts
Part 7: RTOS & Real-Time30 questions — Scheduling, memory, sync
₹199

One-time payment  ·  Lifetime access  ·  No subscription

Unlock Premium Course