Embedded C Interview (50 Questions)

 Section A – C Fundamentals (1–10)

1.

Q1. Explain the memory layout of a C program.

A C program is divided into multiple memory sections by the linker. Each section has a specific purpose.

                High Address
+----------------------------------+
|             Stack                |
| Local Variables                  |
| Function Parameters              |
+----------------------------------+
|                                  |
|             Heap                 |
| malloc(), calloc()               |
+----------------------------------+
|             .bss                 |
| Uninitialized Globals            |
| Static Variables                 |
+----------------------------------+
|             .data                |
| Initialized Globals              |
| Static Variables                 |
+----------------------------------+
|            .rodata               |
| const Variables                  |
| String Literals                  |
+----------------------------------+
|             .text                |
| Program Instructions             |
| Functions                        |
+----------------------------------+
               Low Address

The startup code initializes these sections before calling main().


Q2. What is the .text section?

Answer:

The .text section contains the executable machine instructions of the program.

Contains:
  • Functions
  • Interrupt Service Routines (ISR)
  • Application code
  • Bootloader code
Example:
void add()
{
    printf("Hello");
}
The function add() is stored in the .text section. Characteristics:
  • Read Only
  • Executable
  • Stored in Flash/ROM
  • Cannot be modified during runtime

Q3. What is the .data section?

Answer:

The .data section stores initialized global and static variables.

Example:
int count = 10;

static int flag = 5;
Both variables are placed in the .data section. Characteristics:
  • Read / Write
  • Stored in Flash initially
  • Copied to RAM during startup
  • Modified only in RAM
Startup Flow
Flash
count = 10

↓

Startup Code

↓

RAM
count = 10

Q4. What is the .bss section?

Answer:

The .bss section contains uninitialized global and static variables.

Example:
int counter;

static int errorFlag;
Both variables are stored in the .bss section. Characteristics:
  • Stored only in RAM
  • Automatically initialized to zero
  • Consumes no Flash space for initial values
Startup Code:
memset(__bss_start__, 0, __bss_size__);
This clears all variables in the .bss section before main().

Q5. What is the Heap?

Answer:

The Heap is used for dynamic memory allocation.

Example:
int *ptr;

ptr = malloc(100);
The memory allocated by malloc() comes from the Heap. Characteristics:
  • Allocated during runtime
  • Managed using malloc(), calloc(), realloc(), free()
  • Grows upward (architecture dependent)
Why is Heap avoided in Automotive?
  • Memory Fragmentation
  • Memory Leaks
  • Non-deterministic Allocation Time
  • Difficult Safety Certification
Instead, most automotive projects use:
  • Static Allocation
  • Memory Pools

Q6. What is the Stack?

Answer:

The Stack stores temporary data required during function execution.

Contains:
  • Local Variables
  • Function Parameters
  • Return Address
  • Saved Registers
Example:
void fun()
{
    int a;
    int b;
}
Variables a and b are stored on the Stack. Characteristics:
  • Automatic Allocation
  • Automatic Deallocation
  • Very Fast Access
  • Usually Grows Downward

Q7. Explain the startup sequence of a C program.

Answer:

Power ON

↓

Reset

↓

Reset Handler

↓

Copy .data
(Flash → RAM)

↓

Clear .bss

↓

Initialize C Runtime

↓

Call main()
This initialization happens before executing the application.

Q8. Explain the memory layout using a simple C program.

Answer:

int g = 10;          // .data

int x;               // .bss

const int c = 20;    // .rodata

int main()
{
    int a = 5;            // Stack

    int *p = malloc(20);  // Heap

    return 0;
}
Variable Memory Section
g .data
x .bss
c .rodata
a Stack
p Stack (pointer)
malloc() memory Heap
main() .text

Q9. Why is the .data section copied from Flash to RAM?

Answer:

Initialized global and static variables must be writable during program execution. Their initial values are stored in Flash, but the startup code copies them to RAM so the application can modify them.


Q10. Why is the .bss section not stored in Flash?

Answer:

All variables in the .bss section are initialized to zero. Instead of storing zeros in Flash, the startup code clears the RAM region using memset(), which saves Flash memory.


Q11. Why is the .text section stored in Flash?

Answer:

The .text section contains executable program instructions. Since code normally does not change during execution, storing it in non-volatile Flash memory is efficient and preserves the program across power cycles.


Q12. Why do automotive projects avoid malloc()?

Answer:

  • Memory Fragmentation
  • Memory Leaks
  • Non-deterministic execution time
  • Reduced reliability in safety-critical systems

For these reasons, automotive software typically relies on static memory allocation or fixed-size memory pools instead of dynamic allocation.


2.

What is the difference between:

Q1. What is the difference between const int *p, int *const p, and const int *const p?

Answer

There are two things to remember:
  • Can the data be modified?
  • Can the pointer point to another location?
Declaration Modify Data? Change Pointer?
const int *p ❌ No ✅ Yes
int *const p ✅ Yes ❌ No
const int *const p ❌ No ❌ No

Q2. Explain const int *p.

Answer

This means: Pointer to Constant Integer The pointer can point to another variable, but the value being pointed to cannot be modified through the pointer. Example
int a = 10;
int b = 20;

const int *p = &a;

p = &b;      // ✔ Allowed

*p = 30;     // ✘ Error
Diagram
      p
      │
      ▼
+-----------+
|   a = 10  |
+-----------+

Pointer can move.

Data cannot be modified.

Q3. Explain int *const p.

Answer

This means: Constant Pointer to Integer The pointer always points to the same memory location, but the value at that location can be modified. Example
int a = 10;
int b = 20;

int *const p = &a;

*p = 30;     // ✔ Allowed

p = &b;      // ✘ Error
Diagram
      p (Fixed)
      │
      ▼
+-----------+
|   a = 10  |
+-----------+

Pointer cannot move.

Data can be modified.

Q4. Explain const int *const p.

Answer

This means: Constant Pointer to Constant Integer Neither the pointer nor the data can be modified. Example
int a = 10;
int b = 20;

const int *const p = &a;

*p = 30;      // ✘ Error

p = &b;       // ✘ Error
Diagram
      p (Fixed)
      │
      ▼
+-----------+
|   a = 10  |
+-----------+

Pointer cannot move.

Data cannot be modified.

Q5. How can you easily read these declarations?

Answer

Read from the variable name outward.

Example 1

const int *p;
Read as:
p

↓

Pointer

↓

to const int
Meaning: Pointer is variable. Data is constant.

Example 2

int *const p;
Read as:
p

↓

const Pointer

↓

to int
Meaning: Pointer is constant. Data is variable.

Example 3

const int *const p;
Read as:
p

↓

const Pointer

↓

to const int
Meaning: Both pointer and data are constant.

Q6. Where are these used in Embedded Systems?

Answer

const int *p Used when reading data that must not be modified. Examples:
  • Flash memory
  • Configuration tables
  • Calibration data
  • String literals
Example:
const uint8_t *FirmwareImage;

int *const p Used when a pointer must always reference the same hardware register. Example:
#define GPIO ((volatile uint32_t *)0x40020000)

volatile uint32_t *const GPIOA = GPIO;
The register address never changes, but the register value changes.
const int *const p Used for fixed lookup tables or secure configuration data. Example:
const uint8_t *const BootSignature;
Neither the address nor the contents should change.

Q7. Interview Trick: What is the difference between these two declarations? const int *p; int const *p;

Answer

There is no difference. Both declarations mean:
Pointer to Constant Integer
Both prevent modification of the data through the pointer.

Q8. Quick Interview Summary
Declaration Pointer Constant? Data Constant?
int *p ❌ No ❌ No
const int *p ❌ No ✅ Yes
int *const p ✅ Yes ❌ No
const int *const p ✅ Yes ✅ Yes

3.

Explain the difference between

  • static variable
  • global variable
  • local variable
  • extern variable

4.

When should the volatile keyword be used?

Give practical embedded examples.

Q. When should the volatile keyword be used? Give practical embedded examples.

Answer

The volatile keyword tells the compiler that the value of a variable can change at any time without the compiler's knowledge. Therefore, the compiler must always read the value from memory instead of using an optimized copy stored in a CPU register.

Without volatile, the compiler may optimize the code, leading to incorrect behavior in embedded systems.

Syntax

volatile int flag;

This tells the compiler that flag can change unexpectedly.


Q. Why is volatile required?

Answer

Modern compilers perform aggressive optimizations. If a variable appears unchanged within a function, the compiler may read it once and reuse the value from a CPU register instead of accessing memory repeatedly.

However, in embedded systems, hardware or another execution context can modify the variable at any time.

Example (Without volatile)
int flag = 0;

while(flag == 0)
{
    // Wait
}

The compiler may optimize this as:

int temp = flag;

while(temp == 0)
{
    // Infinite loop
}

If an interrupt changes flag, the loop never exits because the compiler continues using temp.

Correct Code
volatile int flag = 0;

while(flag == 0)
{
    // Wait
}

Now the compiler reads flag from memory on every iteration.


Q. When should volatile be used?

Answer

The volatile keyword should be used whenever a variable can be modified outside the normal program flow.

Common use cases:
  • Hardware registers
  • Interrupt Service Routine (ISR) variables
  • Shared variables between tasks (with proper synchronization)
  • DMA buffers or DMA status flags
  • Memory-mapped peripheral registers
  • Watchdog registers
  • Status registers updated by hardware

Q. Give an example of volatile with an Interrupt Service Routine (ISR).

Answer

Incorrect Code
int dataReady = 0;

void ISR(void)
{
    dataReady = 1;
}

int main()
{
    while(dataReady == 0)
    {
    }
}

The compiler may optimize the loop and never detect the update.

Correct Code
volatile int dataReady = 0;

void ISR(void)
{
    dataReady = 1;
}

int main()
{
    while(dataReady == 0)
    {
    }
}

Each iteration reads the latest value from memory, allowing the main loop to detect the ISR update.


Q. Give an example of volatile with memory-mapped hardware registers.

Answer

Peripheral registers are updated directly by hardware, so they must always be declared as volatile.

#define GPIO_STATUS (*(volatile uint32_t *)0x40020010)

if(GPIO_STATUS & 0x01)
{
    // Button Pressed
}

Every read accesses the actual hardware register instead of a cached value.


Q. Give an example of volatile with DMA.

Answer

DMA controllers update memory independently of the CPU.

volatile uint8_t dmaComplete = 0;

void DMA_IRQHandler(void)
{
    dmaComplete = 1;
}

int main()
{
    while(dmaComplete == 0)
    {
    }

    // Process received data
}

Without volatile, the CPU may never observe the DMA completion flag.


Q. Give an example of volatile with UART communication.

Answer

volatile uint8_t rxFlag = 0;
volatile uint8_t rxData;

void UART_IRQHandler(void)
{
    rxData = UART_DR;
    rxFlag = 1;
}

int main()
{
    while(rxFlag == 0)
    {
    }

    printf("%c", rxData);
}

The interrupt updates the variables, so both should be declared as volatile.


Q. Should every global variable be declared as volatile?

Answer

No. Only variables that can change unexpectedly should be declared as volatile.

Using volatile unnecessarily prevents compiler optimizations and can reduce performance.


Q. Does volatile make code thread-safe?

Answer

No.

volatile only prevents compiler optimization. It does not provide:

  • Atomic operations
  • Mutual exclusion
  • Memory synchronization
  • Race condition protection

For shared data between multiple tasks or threads, use synchronization mechanisms such as:

  • Mutex
  • Semaphore
  • Critical Section
  • Spinlock (where appropriate)
  • Atomic operations

Q. What is the difference between const and volatile?

Answer

Feature const volatile
Purpose Prevents modification by software Prevents compiler optimization
Can value change? No (through that variable) Yes
Compiler optimization Allowed Restricted
Typical use Configuration tables, constants Hardware registers, ISRs, DMA

Q. Can const and volatile be used together?

Answer

Yes. A variable can be both const and volatile.

Example
const volatile uint32_t STATUS_REGISTER =
    *(volatile uint32_t *)0x40000000;

Here:

  • volatile indicates the hardware may update the register at any time.
  • const prevents software from writing to the register.

This combination is commonly used for read-only hardware status registers.


Q. What are some real-time automotive examples where volatile is required?

Answer

  • CAN receive flag updated by a CAN ISR.
  • UART receive buffer updated by an interrupt.
  • DMA completion flag for SPI or ADC transfers.
  • Watchdog status register.
  • GPIO input status register.
  • Timer counter register.
  • ADC conversion complete flag.
  • Bootloader communication status flag.
  • Flash controller busy register.
  • Ethernet MAC status registers.

Q. Quick Interview Summary
Use Case volatile Required?
Hardware Registers ✔ Yes
ISR Shared Variables ✔ Yes
DMA Buffers/Flags ✔ Yes
Memory-Mapped I/O ✔ Yes
Normal Local Variables ✘ No
Normal Global Variables ✘ No (unless updated externally)
Thread Safety ✘ No

5.

What is the difference between

malloc()
calloc()
realloc()

When are they avoided in embedded systems?

Q. What is the difference between malloc(), calloc(), and realloc()? When are they avoided in embedded systems?

Answer

These are standard C library functions used for dynamic memory allocation from the Heap.

Function Purpose Initializes Memory? Can Resize Memory?
malloc() Allocates a block of memory ❌ No ❌ No
calloc() Allocates memory for multiple elements ✔ Yes (Zero-initialized) ❌ No
realloc() Resizes an existing memory block Existing data preserved (up to the smaller of old/new sizes) ✔ Yes

All three functions allocate memory from the Heap.


Q. What is malloc()?

Answer

malloc() allocates a specified number of bytes from the Heap.

Syntax
void *malloc(size_t size);
Example
int *ptr;

ptr = (int *)malloc(sizeof(int) * 5);

if(ptr != NULL)
{
    // Use memory
}
Characteristics
  • Allocates a contiguous block of memory.
  • Memory contents are uninitialized (contain indeterminate values).
  • Returns NULL if allocation fails.
  • Memory must be released using free().

Q. What is calloc()?

Answer

calloc() allocates memory for an array of elements and initializes all bytes to zero.

Syntax
void *calloc(size_t num, size_t size);
Example
int *ptr;

ptr = (int *)calloc(5, sizeof(int));

if(ptr != NULL)
{
    // All elements are initialized to 0
}
Characteristics
  • Allocates contiguous memory.
  • Initializes allocated memory to zero.
  • Returns NULL on failure.
  • Memory must be released using free().

Q. What is realloc()?

Answer

realloc() changes the size of an existing memory block.

Syntax
void *realloc(void *ptr, size_t new_size);
Example
int *ptr;

ptr = (int *)malloc(5 * sizeof(int));

int *temp = (int *)realloc(ptr, 10 * sizeof(int));

if(temp != NULL)
{
    ptr = temp;
}
else
{
    // Original memory pointed to by ptr is still valid
}
Characteristics
  • Can increase or decrease the allocated memory size.
  • May move the allocation to a new location if needed.
  • Preserves existing data up to the smaller of the old and new sizes.
  • If realloc() fails, it returns NULL and the original memory block remains allocated.

Q. What is the difference between malloc(), calloc(), and realloc()?

Answer

Feature malloc() calloc() realloc()
Purpose Allocate memory Allocate and zero-initialize memory Resize existing memory
Initialization No Yes (Zero) Existing data preserved
Arguments 1 2 2
May move memory No No Yes
Returns Pointer or NULL Pointer or NULL Pointer or NULL

Q. Why are these functions often avoided in embedded systems?

Answer

Many embedded systems, especially automotive and safety-critical applications, avoid dynamic memory allocation because it introduces unpredictability and reliability risks.

Main reasons:
  • Memory fragmentation over time.
  • Memory leaks if allocated memory is not freed.
  • Non-deterministic allocation time.
  • Heap exhaustion.
  • Difficult debugging and validation.
  • Harder to meet real-time requirements.
  • Challenges in safety certification (e.g., ISO 26262).

Q. What is memory fragmentation?

Answer

Memory fragmentation occurs when free memory is split into many small blocks, making it impossible to allocate a larger contiguous block even though the total free memory is sufficient.

Example
Initial Heap

+-----------------------------+
| Free 100 Bytes             |
+-----------------------------+

After several allocations/frees

+-----+----+------+----+------+
|Free |Used|Free  |Used|Free  |
+-----+----+------+----+------+

Total Free = 60 Bytes

Largest Continuous Block = 20 Bytes

A request for 30 bytes may fail because no single contiguous block is large enough.


Q. What alternatives are commonly used instead of dynamic memory allocation?

Answer

Embedded systems typically prefer deterministic memory management techniques.

  • Static memory allocation.
  • Global variables.
  • Fixed-size buffers.
  • Memory pools.
  • Static RTOS object allocation.
Example
static uint8_t rxBuffer[256];

The memory is allocated at compile time and remains available throughout program execution.


Q. Can malloc() be used in embedded systems?

Answer

Yes, but only when appropriate for the application.

Examples include:

  • Linux applications.
  • Linux device drivers (using kernel allocation APIs such as kmalloc(), not standard malloc()).
  • Embedded Linux systems with memory management.
  • Systems where dynamic allocation is controlled and predictable.

In bare-metal and safety-critical firmware, dynamic allocation is generally minimized or avoided after system initialization.


Q. What precautions should be taken when using realloc()?

Answer

Never assign the result of realloc() directly to the original pointer because the original memory would be lost if the reallocation fails.

Incorrect
ptr = realloc(ptr, new_size);
Correct
int *temp = realloc(ptr, new_size);

if(temp != NULL)
{
    ptr = temp;
}
else
{
    // Original pointer is still valid
}

Q. Quick Interview Summary
Function Purpose Memory Initialized?
malloc() Allocate memory ❌ No
calloc() Allocate and zero-initialize memory ✔ Yes
realloc() Resize allocated memory Existing data preserved

Why avoided in embedded systems?
  • Memory fragmentation
  • Memory leaks
  • Non-deterministic execution time
  • Heap exhaustion
  • Reduced reliability in real-time and safety-critical systems

6.

What are lvalues and rvalues?

Q. What are lvalues and rvalues?

Answer

In C, every expression is classified as either an lvalue or an rvalue.

  • lvalue (Left Value): Represents an object that has a memory location (address). It can appear on the left-hand side of an assignment.
  • rvalue (Right Value): Represents a temporary value or constant that does not have a persistent memory location. It typically appears on the right-hand side of an assignment.
Example
int a = 10;
  • a is an lvalue.
  • 10 is an rvalue.

Q. What is an lvalue?

Answer

An lvalue is an expression that refers to an object with a valid memory location.

Characteristics
  • Has an address in memory.
  • Can appear on the left-hand side of an assignment.
  • Can also be used on the right-hand side.
Example
int x = 5;

x = 20;
Here, x is an lvalue because it occupies a memory location and its value can be modified.

Q. What is an rvalue?

Answer

An rvalue is an expression that represents a value but does not refer to a persistent memory location that you can assign to.

Characteristics
  • Usually appears on the right-hand side of an assignment.
  • Cannot be assigned a new value.
  • Often represents literals, temporary values, or the result of an expression.
Examples
10

x + y

func()
These expressions produce values but cannot be assigned to.

Q. Give examples of lvalues and rvalues.

Answer

Example 1
int a = 10;
  • a → lvalue
  • 10 → rvalue

Example 2
int x = 5;
int y = x;
  • x is an lvalue.
  • Its value is used as an rvalue during the assignment to y.

Example 3
int sum = x + y;
  • x → lvalue
  • y → lvalue
  • x + y → rvalue

Q. Which expressions are invalid because they are not lvalues?

Answer

Invalid Example 1
10 = x;
Error:
Left operand must be an lvalue.

Invalid Example 2
(x + y) = 100;
Error:

The result of x + y is a temporary value (rvalue), so it cannot be assigned to.


Valid Example
x = y + 10;
Here:
  • x is an lvalue.
  • y + 10 is an rvalue.

Q. Are array elements lvalues?

Answer

Yes.

Example
int arr[5];

arr[0] = 100;

arr[0] refers to a memory location, so it is an lvalue.


Q. Is a pointer dereference an lvalue?

Answer

Yes.

Example
int x = 10;
int *p = &x;

*p = 50;

*p refers to the memory location of x, making it an lvalue.


Q. Is a function return value an lvalue?

Answer

In most cases, no. If a function returns by value, the returned expression is an rvalue.

Example
int getValue(void)
{
    return 10;
}

getValue() = 20;

This is invalid because the function returns a temporary value.


Q. What is the difference between lvalues and rvalues?

Answer

Feature lvalue rvalue
Has a memory location ✔ Yes ❌ Usually No
Can appear on left side of assignment ✔ Yes ❌ No
Can appear on right side of assignment ✔ Yes ✔ Yes
Represents a temporary value ❌ No ✔ Yes
Examples Variables, array elements, *pointer Literals, expressions, function return values

Q. Give practical embedded examples of lvalues and rvalues.

Answer

Example 1: GPIO Register
#define GPIO_OUT (*(volatile uint32_t *)0x40020000)

GPIO_OUT = 0x01;

GPIO_OUT is an lvalue because it refers to a hardware register with a fixed memory address.


Example 2: ADC Result
uint16_t adcValue;

adcValue = ADC_DATA_REGISTER;
  • adcValue → lvalue
  • ADC_DATA_REGISTER → lvalue (memory-mapped register)
  • The value read from the register is used as an rvalue during the assignment.

Example 3: Bootloader Status Flag
volatile uint8_t bootStatus;

bootStatus = 1;

bootStatus is an lvalue because it occupies a memory location that can be updated by software or hardware.


Q. Quick Interview Summary
Expression lvalue / rvalue
int x;x lvalue
100 rvalue
x + y rvalue
arr[0] lvalue
*ptr lvalue
func() (returns by value) rvalue

7.

What is undefined behavior?

Give five examples.

Q. What is undefined behavior? Give five examples.

Answer

Undefined Behavior (UB) is a situation where the C language standard does not define what the program should do. When undefined behavior occurs, the compiler is free to produce any result, including:

  • Correct output
  • Incorrect output
  • Program crash
  • Unexpected behavior
  • Different results with different compilers or optimization levels

Since the behavior is undefined, developers must avoid writing code that relies on it.

Example 1: Using an Uninitialized Variable

int x;
printf("%d", x);

Why? The variable x contains an indeterminate value because it was never initialized.


Example 2: Dereferencing a NULL Pointer

int *ptr = NULL;

*ptr = 10;

Why? Dereferencing a NULL pointer results in undefined behavior and often causes a program crash.


Example 3: Accessing an Array Out of Bounds

int arr[5];

arr[5] = 100;

Why? Valid indices are 0 to 4. Accessing arr[5] writes outside the array boundaries.


Example 4: Modifying a Variable Multiple Times Without a Sequence Point

int i = 5;

i = i++;

Why? The expression modifies i and also uses its value in the same expression without a well-defined sequencing, resulting in undefined behavior.


Example 5: Signed Integer Overflow

int x = INT_MAX;

x = x + 1;

Why? Overflowing a signed integer is undefined behavior according to the C standard.

Why is Undefined Behavior Dangerous in Embedded Systems?

  • May cause random system crashes.
  • Can produce different behavior with different compiler optimization levels.
  • Makes debugging difficult.
  • May lead to unpredictable ECU behavior in safety-critical systems.
  • Can introduce intermittent field failures.

Quick Interview Tip

Interviewers often ask:

"Can undefined behavior produce the correct output?"

Answer: Yes. Undefined behavior may appear to work correctly during testing, but it is not reliable because the C standard provides no guarantee about its behavior. The same code may fail after recompilation, optimization changes, or when run on different hardware.


8.

Difference between

char *p = "Hello";
char p[] = "Hello";
Q. What is the difference between char *p = "Hello"; and char p[] = "Hello";?

Answer

Although both declarations appear similar, they are fundamentally different in terms of memory allocation, modifiability, and storage.

Example 1

char *p = "Hello";
  • p is a pointer.
  • The string literal "Hello" is typically stored in the .rodata (read-only data) section.
  • p stores the address of the first character.
  • The pointer can point to another string.
  • Modifying the string through p results in undefined behavior.
Memory Layout
        +-----+
p ----> | 'H' |
        | 'e' |
        | 'l' |
        | 'l' |
        | 'o' |
        | '\0'|
        +-----+
Example
char *p = "Hello";

p = "World";      // ✔ Allowed

p[0] = 'h';       // ✘ Undefined Behavior

Example 2

char p[] = "Hello";
  • p is an array.
  • A copy of the string is stored in the array.
  • The array elements can be modified.
  • The array name cannot be reassigned.
Memory Layout
+-----+-----+-----+-----+-----+------+
| 'H' | 'e' | 'l' | 'l' | 'o' | '\0' |
+-----+-----+-----+-----+-----+------+
        p
Example
char p[] = "Hello";

p[0] = 'h';      // ✔ Allowed

p = "World";     // ✘ Error

Comparison

Feature char *p = "Hello" char p[] = "Hello"
Type Pointer Character Array
Memory Points to string literal Stores its own copy
Can modify characters? ❌ No (Undefined Behavior) ✔ Yes
Can point elsewhere? ✔ Yes ❌ No
Typical Storage .rodata (string literal) Stack (local) or .data (global/static)

Embedded Interview Tip

For constant strings that never change (e.g., firmware version, diagnostic messages), a pointer to a string literal is commonly used. If the string needs to be modified, use a character array.


9.

Why is C preferred over C++ in embedded firmware?

Q. Why is C preferred over C++ in embedded firmware?

Answer

C has been the dominant programming language for embedded firmware for decades because it provides direct hardware access, deterministic execution, minimal runtime overhead, and excellent portability. While modern embedded systems also use C++, C is still widely preferred for bare-metal and safety-critical firmware.

Reasons C is Preferred

  1. Direct Hardware Access

    C provides direct access to memory-mapped hardware registers using pointers.

    #define GPIO_OUT (*(volatile uint32_t *)0x40020000)
    
    GPIO_OUT = 0x01;
    

    This makes it ideal for writing device drivers, bootloaders, and peripheral control software.

  2. Minimal Runtime Overhead

    C has no hidden runtime features such as constructors, destructors, virtual tables (vtables), Runtime Type Information (RTTI), or exception handling.

    This results in predictable execution and efficient use of CPU and memory resources.

  3. Deterministic Execution

    Embedded systems, especially real-time applications, require predictable execution times. C generates straightforward machine code with minimal hidden operations.

  4. Smaller Code Size

    C programs generally produce smaller executables, which is important for microcontrollers with limited Flash and RAM.

  5. Easy Startup and Bootloader Development

    Reset handlers, startup code, interrupt vector tables, linker scripts, and bootloaders are traditionally implemented in C because they interact directly with the hardware and processor initialization.

  6. Wide Compiler and Hardware Support

    Almost every microcontroller vendor provides C compilers, SDKs, BSPs, and peripheral libraries.

  7. Simpler Certification

    Safety-critical industries such as automotive, aerospace, and medical devices often prefer C because its behavior is easier to analyze and certify under standards such as ISO 26262 and IEC 61508.

C vs C++ Comparison

Feature C C++
Direct Hardware Access ✔ Excellent ✔ Excellent
Runtime Overhead Very Low Higher (depends on features used)
Code Size Smaller Can be larger
Deterministic Execution ✔ High Depends on language features
Object-Oriented Programming ❌ No ✔ Yes
Templates ❌ No ✔ Yes
Exceptions ❌ No ✔ Yes (often disabled in embedded projects)
RTTI ❌ No ✔ Yes (often disabled in embedded projects)

Is C++ Used in Embedded Systems?

Yes. Modern embedded systems increasingly use C++, especially in:

  • Embedded Linux
  • Automotive AUTOSAR Adaptive
  • ADAS and Autonomous Driving
  • Industrial Automation
  • Robotics
  • IoT Gateways

However, many projects use only a controlled subset of C++ and avoid features such as exceptions, RTTI, and unrestricted dynamic memory allocation to maintain deterministic behavior.

Senior Interview Answer

C is preferred in embedded firmware because it provides direct hardware access, deterministic execution, low runtime overhead, and small code size. It integrates well with startup code, linker scripts, bootloaders, and device drivers. Although modern embedded systems also use C++, especially for complex applications, many safety-critical firmware projects continue to use C because of its simplicity, predictability, and easier certification. In practice, the choice depends on the application requirements rather than the language alone.

10.

Explain the compilation process.

.c


Preprocessor



Compiler



Assembler



Linker



ELF/BIN
Q. Explain the compilation process in C.

Answer

The compilation process converts a C source file into an executable program through multiple stages. Each stage performs a specific task before generating the final executable (ELF) or binary (BIN/HEX) file.

Source File (.c)

        │
        ▼
+----------------+
| Preprocessor   |
+----------------+
        │
        ▼
Expanded Source (.i)

        │
        ▼
+----------------+
| Compiler       |
+----------------+
        │
        ▼
Assembly File (.s)

        │
        ▼
+----------------+
| Assembler      |
+----------------+
        │
        ▼
Object File (.o)

        │
        ▼
+----------------+
| Linker         |
+----------------+
        │
        ▼
Executable (.elf)

        │
        ▼
Objcopy

        │
        ▼
Binary (.bin/.hex)

1. Preprocessor

The preprocessor processes all preprocessor directives before compilation.

Tasks Performed
  • Expands #include files.
  • Expands #define macros.
  • Removes comments.
  • Processes conditional compilation (#ifdef, #ifndef, #if).
Example
#define PI 3.14

printf("%f", PI);

After preprocessing:

printf("%f", 3.14);
Output File
main.i

2. Compiler

The compiler converts the preprocessed C code into assembly language.

Tasks Performed
  • Syntax checking.
  • Type checking.
  • Optimization.
  • Generates assembly code.
Example
x = a + b;

May become:

LDR R0, [a]
LDR R1, [b]
ADD R2, R0, R1
STR R2, [x]
Output File
main.s

3. Assembler

The assembler converts assembly instructions into machine code.

Tasks Performed
  • Converts assembly instructions into binary instructions.
  • Creates symbol tables.
  • Generates relocation information.
Output File
main.o

The object file contains machine code but is not yet executable because external symbols may still be unresolved.


4. Linker

The linker combines one or more object files and libraries to create the final executable.

Tasks Performed
  • Combines object files.
  • Links static libraries.
  • Resolves external function and variable references.
  • Assigns memory addresses using the linker script.
  • Generates the memory map.
Example
main.o

driver.o

startup.o

↓

Application.elf
Output File
Application.elf

5. ELF File

The ELF (Executable and Linkable Format) file contains:

  • Executable machine code.
  • Debug information.
  • Symbol table.
  • Memory section information (.text, .data, .bss, etc.).

The ELF file is mainly used for debugging and programming during development.


6. BIN / HEX File

The ELF file is converted into a binary or Intel HEX file before programming the microcontroller.

Application.elf

↓

objcopy

↓

Application.bin

or

Application.hex

These files contain only the information required to program the Flash memory.


Compilation Flow Summary

Stage Input Output Main Purpose
Preprocessor .c .i Expand macros and headers
Compiler .i .s Generate assembly code
Assembler .s .o Generate machine code
Linker .o .elf Resolve symbols and create executable
Objcopy .elf .bin / .hex Create Flash programming image

Practical Automotive Example

Consider a bootloader project:

main.c
flash_driver.c
can_driver.c
uds_server.c
startup.s
linker.ld

Build Process:

.c files
      │
      ▼
Preprocessor
      │
      ▼
Compiler
      │
      ▼
Assembler
      │
      ▼
main.o
flash_driver.o
can_driver.o
uds_server.o
startup.o
      │
      ▼
Linker
      │
      ▼
Bootloader.elf
      │
      ▼
objcopy
      │
      ▼
Bootloader.bin

The generated .bin file is programmed into the ECU's Flash memory using tools such as CANoe, Lauterbach, UDS Flash Bootloader, or production programming tools.


Senior Interview Answer

The compilation process consists of five stages: Preprocessor, Compiler, Assembler, Linker, and Objcopy. The preprocessor expands macros and header files, the compiler converts C code into assembly, the assembler generates object files containing machine code, and the linker combines all object files and libraries into an ELF executable while assigning memory addresses according to the linker script. Finally, the ELF is converted into a BIN or HEX file, which is programmed into the microcontroller's Flash memory.

Section B – Pointers (11–20)

11.

Explain every type of pointer in C.

  • NULL pointer
  • Void pointer
  • Wild pointer
  • Dangling pointer
  • Function pointer
Q. Explain the different types of pointers in C.

Answer

Pointers are variables that store memory addresses. Based on their state and usage, pointers are commonly classified into the following types:

  1. NULL Pointer
  2. Void Pointer (Generic Pointer)
  3. Wild Pointer
  4. Dangling Pointer
  5. Function Pointer

1. NULL Pointer

A NULL pointer is a pointer that does not point to any valid memory location. It is initialized with NULL.

Example
int *ptr = NULL;

if(ptr == NULL)
{
    printf("Pointer is NULL");
}
Characteristics
  • Points to no valid object.
  • Used to indicate an invalid or uninitialized reference.
  • Dereferencing a NULL pointer results in Undefined Behavior.

2. Void Pointer (Generic Pointer)

A void pointer can store the address of any data type. Since it has no associated data type, it must be cast before dereferencing.

Example
int x = 100;

void *ptr = &x;

printf("%d", *(int *)ptr);
Characteristics
  • Can point to any data type.
  • Cannot be dereferenced directly.
  • Requires explicit type casting.
  • Widely used in generic APIs such as malloc() and callback interfaces.

3. Wild Pointer

A wild pointer is a pointer that has been declared but not initialized. It contains an indeterminate address.

Example
int *ptr;

*ptr = 10;     // Undefined Behavior
Characteristics
  • Contains a random memory address.
  • Dereferencing it may crash the program or corrupt memory.
  • Always initialize pointers to NULL or a valid address.

4. Dangling Pointer

A dangling pointer points to memory that is no longer valid.

Example
int *ptr = (int *)malloc(sizeof(int));

free(ptr);

*ptr = 20;     // Undefined Behavior

After free(), the memory is released, but ptr still holds the old address.

Correct Practice
free(ptr);

ptr = NULL;
Characteristics
  • Points to deallocated or invalid memory.
  • May cause crashes or data corruption.
  • Set pointers to NULL after freeing memory.

5. Function Pointer

A function pointer stores the address of a function and allows the function to be called indirectly.

Example
int add(int a, int b)
{
    return a + b;
}

int (*fp)(int, int);

fp = add;

printf("%d", fp(10, 20));
Characteristics
  • Stores the address of a function.
  • Used for callbacks.
  • Commonly used in RTOS, interrupt vector tables, bootloaders, and driver frameworks.

Comparison

Pointer Type Description Safe to Dereference?
NULL Pointer Points to no valid object ❌ No
Void Pointer Generic pointer to any data type ✔ Yes (after type casting)
Wild Pointer Uninitialized pointer ❌ No
Dangling Pointer Points to freed or invalid memory ❌ No
Function Pointer Stores the address of a function ✔ Yes

Practical Embedded Examples

  • NULL Pointer: Used to indicate that a peripheral driver has not been initialized.
  • Void Pointer: Used in generic APIs such as memcpy(), RTOS task parameters, and malloc().
  • Wild Pointer: Can occur if a driver pointer is declared but never initialized before accessing a hardware register.
  • Dangling Pointer: May occur in Embedded Linux applications after calling free().
  • Function Pointer: Used in interrupt vector tables, callback registration, state machines, bootloaders, and communication stacks.

Quick Interview Summary

Pointer Type Definition
NULL Pointer Points to no valid memory location
Void Pointer Generic pointer that can hold any address
Wild Pointer Declared but not initialized
Dangling Pointer Points to memory that is no longer valid
Function Pointer Stores the address of a function

Senior Interview Tip

A common follow-up question is:

"What is the difference between a Wild Pointer and a Dangling Pointer?"

Answer:

  • A Wild Pointer has never been initialized and contains an indeterminate address.
  • A Dangling Pointer was once valid, but the memory it pointed to has been freed or has gone out of scope.

12.

Output?

int a=5;
int *p=&a;

printf("%d",*p++);

Explain carefully.

Q. What is the output of the following program? Explain carefully.
int a = 5;
int *p = &a;

printf("%d", *p++);

Answer

Output
5

The expression *p++ is often confusing because both the dereference operator (*) and the post-increment operator (++) are used together.

Step 1: Understand Operator Precedence

The post-increment operator (++) has higher precedence than the dereference operator (*).

Therefore, the compiler interprets the expression as:
*(p++)

Not as:

(*p)++

Step 2: Initial State

a = 5

        +-----+
0x100 -->|  5  |
        +-----+

p = 0x100

The pointer p points to variable a.


Step 3: Evaluate p++

The post-increment operator returns the current value of the pointer and then increments it.

Current p = 0x100

Return 0x100

Increment p

Step 4: Dereference

The returned pointer (0x100) is dereferenced.

*(0x100)

↓

5

Therefore, printf() prints:

5

Step 5: Final State

After the expression completes:

p = p + 1;

If p is an int *, it advances by sizeof(int) bytes.

Before

p ----> a

After

p ----> Next int location

In this example, p no longer points to a. Dereferencing it afterwards would result in undefined behavior because it points past the only object.


Difference Between Similar Expressions

Expression Meaning
*p++ *(p++) → Read value first, then increment the pointer.
(*p)++ Increment the value pointed to by p.
*++p Increment the pointer first, then dereference the new location.
++*p Increment the value pointed to by p, then use the incremented value.

Example Comparison

int a = 5;
int *p = &a;
Statement Output Value of a Pointer Changed?
printf("%d", *p++); 5 5 ✔ Yes
printf("%d", (*p)++); 5 6 ❌ No
printf("%d", ++*p); 6 6 ❌ No

Senior Interview Tip

This is one of the most frequently asked pointer questions in Embedded C interviews.

A simple way to remember it is:

  • *p++ → Move the pointer after reading the value.
  • (*p)++ → Increment the value, not the pointer.
  • *++p → Move the pointer first, then read the value.
  • ++*p → Increment the value first, then use it.

13.

Difference

*p++

(*p)++

*(p++)

14.

Write a function to swap two numbers

  • without third variable
  • using pointers

15.

Why are function pointers heavily used in embedded systems?

Give AUTOSAR examples.


16.

Explain pointer arithmetic.


17.

Can pointer arithmetic be done on

void *

Why?


18.

Difference between

int (*fp)(void);

int *fp(void);

19.

What happens if

free(ptr);

free(ptr);

20.

Difference between

memcpy()

memmove()

Section C – Structures & Memory (21–28)

21.

Difference

struct

union

22.

Explain structure padding.

Why does it happen?


23.

How do you reduce padding?


24.

What is packing?

#pragma pack

When should it NOT be used?


25.

Output?

struct A
{
char c;
int i;
short s;
};

Predict structure size on a 32-bit ARM.


26.

Bitfields

Advantages and disadvantages?


27.

Why are bitfields generally avoided in automotive firmware?


28.

Explain endianess.

Little vs Big Endian.


Section D – Embedded Programming (29–38)

29.

Explain memory-mapped I/O.


30.

Why is

volatile

mandatory for peripheral registers?


31.

Difference between

volatile

const

volatile const

32.

Write a macro to SET, CLEAR and TOGGLE bits.


33.

Difference between

#define

const

enum

34.

Write a delay routine.

Why is software delay a bad idea?


35.

What happens if an interrupt modifies a shared variable?


36.

How do you protect shared resources?


37.

Why shouldn't

printf()

be used inside an ISR?


38.

What coding guidelines do you follow?

Explain

  • MISRA C
  • CERT C

Section E – Advanced C (39–45)

39.

Difference

#define MAX 100

const int max=100;

40.

Difference

inline

macro

41.

Recursive function

Advantages

Disadvantages


42.

Explain linker scripts.


43.

What is a startup file?

What happens before

main()

is executed?


44.

Explain weak symbols.


45.

Explain how static libraries differ from shared libraries.


Section F – Scenario Questions (46–50)

46.

Your firmware suddenly crashes after running for 3 days.

How will you debug it?


47.

CPU utilization suddenly reaches 100%.

Possible reasons?


48.

Firmware hangs after enabling interrupts.

How will you debug?


49.

Bootloader jumps to the application, but the application never starts.

Possible reasons?


50.

An ECU works perfectly in Debug mode but fails in Release mode.

What are the possible reasons?


Interviewer's Bonus Questions (Very Common)

These are frequently asked as follow-ups:

  • What is the difference between volatile and atomic?
  • Why is volatile not thread-safe?
  • Explain sequence points.
  • Explain strict aliasing.
  • Why should dynamic memory allocation be avoided in embedded systems?
  • How does the linker resolve multiple definitions?
  • Explain symbol tables.
  • What is a linker map file?
  • Explain the difference between .elf, .hex, and .bin.
  • Explain startup code in ARM Cortex-M.
  • What happens when you dereference a NULL pointer?
  • What is memory fragmentation?
  • Why are function pointers useful in state machines?
  • Explain restrict in C99.
  • What are common causes of stack overflow in embedded systems?

Comments

Popular posts from this blog

Fundamental Secure Features in Automotive ECUs

Overview of ISO/SAE 21434 Standard

Fundamental Secure Feature I: Secure Boot