Skip to content
RED SAGSkills

Embedded C interview questions and answers

Embedded C questions check low-level skills: memory, bit manipulation and code that talks to hardware.

Below are 60 Embedded C questions with the correct answer and a short explanation, grouped from fresher to experienced level. Try each one before you open the answer.

What Embedded C interviews usually check

  • ✓Pointers and memory layout
  • ✓volatile, const and static
  • ✓Bit manipulation and registers
  • ✓Interrupts and ISRs
  • ✓Fixed-width types and portability

Embedded C interview questions for freshers easy

1. Setting a register bit

Which C statement sets bit 5 of REG without changing the other bits?

  1. A.REG = (1 << 5);
  2. B.REG &= (1 << 5);
  3. C.REG |= (1U << 5);
  4. D.REG ^= 5;
Show answer

Answer: C. REG |= (1U << 5);

OR with a mask sets the chosen bit and leaves others unchanged. Plain assignment clears all other bits, AND keeps only bit 5, and XOR with 5 toggles bits 0 and 2. To clear bit 5, use REG &= ~(1U << 5).

2. Adding to a uint8_t counter

What does this code print?

uint8_t x = 250;
x += 10;
printf("%u", (unsigned)x);
  1. A.260
  2. B.255
  3. C.4
  4. D.10
Show answer

Answer: C. 4

The sum is 260, but storing it back into an 8-bit unsigned variable keeps only the value modulo 256, so x becomes 4. Unsigned wraparound is well defined in C. It does not saturate at 255, which is a common wrong assumption.

3. First byte in memory

On a little-endian MCU, uint32_t v = 0x12345678; is stored at address A. Which byte is at address A?

  1. A.0x12
  2. B.0x78
  3. C.0x34
  4. D.0x56
Show answer

Answer: B. 0x78

Little-endian stores the least significant byte at the lowest address, so the bytes in memory are 78 56 34 12. Most ARM Cortex-M and RISC-V MCUs run little-endian. Network protocols are often big-endian, so you must convert explicitly.

4. A macro without parentheses

What does this code print?

#define SQUARE(x) x * x

printf("%d", SQUARE(1 + 2));
  1. A.9
  2. B.6
  3. C.3
  4. D.5
Show answer

Answer: D. 5

Macros do plain text substitution, so SQUARE(1 + 2) becomes 1 + 2 * 1 + 2, which is 5. The safe form is #define SQUARE(x) ((x) * (x)), or better a static inline function. MISRA C restricts function-like macros for this reason.

5. Passing an array to a function

What does the function actually receive when you call send(buf) with uint8_t buf[64]?

void send(uint8_t *data);
uint8_t buf[64];
send(buf);
  1. A.A copy of all 64 bytes
  2. B.The value of buf[0]
  3. C.The length of the array
  4. D.A pointer to buf[0]
Show answer

Answer: D. A pointer to buf[0]

In most expressions an array name converts ("decays") to a pointer to its first element. The function gets only that address, not a copy and not the length, so the size must be passed separately.

6. Toggling an LED bit

Which statement toggles bit 5 of PORTB and leaves the other bits unchanged?

  1. A.PORTB |= (1u << 5);
  2. B.PORTB ^= (1u << 5);
  3. C.PORTB &= ~(1u << 5);
  4. D.PORTB = (1u << 5);
Show answer

Answer: B. PORTB ^= (1u << 5);

XOR with a mask flips the bits where the mask has a 1 and leaves the rest alone. |= sets the bit, &= ~ clears it, and plain assignment overwrites every bit of the port.

7. Subtracting two pointers

What is the value of n?

uint16_t samples[100];
uint16_t *start = &samples[10];
uint16_t *end   = &samples[35];
ptrdiff_t n = end - start;
  1. A.50
  2. B.35
  3. C.12
  4. D.25
Show answer

Answer: D. 25

Subtracting pointers into the same array gives the number of elements between them, not bytes. 35 − 10 = 25 elements, even though the byte distance is 50.

8. A read-only buffer parameter

A function calculates a CRC over a buffer and must not change it. What is the best parameter type?

  1. A.uint8_t *const data
  2. B.const uint8_t *data
  3. C.uint8_t *data
  4. D.volatile uint8_t *data
Show answer

Answer: B. const uint8_t *data

const uint8_t *data means the function cannot modify the bytes it points to, which documents the contract and lets callers pass const tables stored in flash. uint8_t *const only stops the pointer itself from changing.

9. Which way the stack grows

On Cortex-M, the initial stack pointer is usually set to the top of RAM. Which way does the stack grow as functions are called?

  1. A.Upwards, towards higher addresses
  2. B.It does not grow; its size is fixed per call
  3. C.It alternates between both ends of RAM
  4. D.Downwards, towards lower addresses
Show answer

Answer: D. Downwards, towards lower addresses

Cortex-M uses a full-descending stack: a push first decrements SP and then stores. That is why the stack starts at the top of RAM and grows down towards the heap and static data.

10. static on a global variable

What does static do when placed in front of a variable at file scope in uart.c?

static uint8_t tx_busy;
  1. A.Places it in flash memory
  2. B.Makes it read-only after start-up
  3. C.Limits its visibility to uart.c
  4. D.Creates a new copy for each call
Show answer

Answer: C. Limits its visibility to uart.c

At file scope, static gives internal linkage: the name is visible only inside that translation unit. Other files cannot reach it with extern, which hides driver state and avoids name clashes. The variable is still in RAM.

11. Sharing a global between files

A global counter is needed in several .c files. What is the usual pattern?

  1. A.Define the variable in the header file itself
  2. B.extern declaration in a header, definition in one .c file
  3. C.Define it with static in every .c file
  4. D.Pass it only as a macro value
Show answer

Answer: B. extern declaration in a header, definition in one .c file

extern uint32_t tick_count; in a header declares the name without creating storage. Exactly one .c file defines it, uint32_t tick_count;, so the linker sees one object that every file shares.

12. A big buffer inside a function

A task with a 1 KB stack calls a function that declares uint8_t buf[2048]; as a local. What is likely to happen?

  1. A.The compiler moves buf to flash automatically
  2. B.The stack overflows and corrupts nearby memory
  3. C.buf is placed in .bss without using the stack
  4. D.Only the used part of buf takes stack space
Show answer

Answer: B. The stack overflows and corrupts nearby memory

Automatic arrays are allocated on the stack when the function is entered. 2 KB cannot fit in a 1 KB stack, so it overflows into whatever lies below. Make large buffers static or global, or give the task a bigger stack.

13. Recursion in firmware

Why do many embedded coding standards, including MISRA C, discourage recursion?

  1. A.Stack depth becomes hard to bound and verify
  2. B.Microcontrollers cannot execute recursive calls
  3. C.Recursive functions cannot use local variables
  4. D.Compilers refuse to compile recursion for ARM
Show answer

Answer: A. Stack depth becomes hard to bound and verify

Each recursive call uses more stack, and the depth often depends on runtime data. On a small, fixed stack this makes worst-case usage hard to prove. An iterative loop has a fixed, known stack cost.

14. An undefined reference error

The build fails with "undefined reference to `spi_init'". The header spi.h is included in main.c. What is the most likely cause?

  1. A.spi.h is missing an include guard
  2. B.spi.c is not compiled or not linked into the build
  3. C.main.c has a syntax error near the call
  4. D.The stack is too small for spi_init
Show answer

Answer: B. spi.c is not compiled or not linked into the build

The header gives the compiler a declaration, so main.c compiles. The linker then needs the actual function, which lives in spi.o. If spi.c is not in the build or the library is not linked, the linker reports an undefined reference.

15. The register keyword today

What effect does the register keyword usually have with modern GCC or Clang at -O2?

register uint32_t i;
  1. A.Almost none; the compiler allocates registers itself
  2. B.It forces i into R0 for the whole function
  3. C.It makes i shared with interrupt handlers
  4. D.It doubles the speed of every loop using i
Show answer

Answer: A. Almost none; the compiler allocates registers itself

Modern compilers do register allocation far better than hand hints, so register is mostly ignored for optimisation. Its one remaining effect in C is that you cannot take the address of such a variable.

16. Shrinking a struct by reordering

On a 32-bit ARM target, how many bytes does reordering these members largest-first save in each struct?

struct a { uint8_t x; uint32_t y; uint8_t z; };   /* original */
struct b { uint32_t y; uint8_t x; uint8_t z; };   /* reordered */
  1. A.0 bytes; both are 6
  2. B.2 bytes (8 down to 6)
  3. C.6 bytes (12 down to 6)
  4. D.4 bytes (12 down to 8)
Show answer

Answer: D. 4 bytes (12 down to 8)

struct a needs 3 padding bytes after x so y is 4-aligned, and 3 more after z to keep the size a multiple of 4: 12 bytes. struct b packs x and z after y and pads 2 bytes: 8 bytes. In an array of 1,000 records that saves 4 KB of RAM.

17. Storing -1 in a uint8_t

What value does x hold?

uint8_t x = -1;
  1. A.-1
  2. B.127
  3. C.0
  4. D.255
Show answer

Answer: D. 255

Converting a negative value to an unsigned type is well defined: it wraps modulo 2^8. −1 + 256 = 255, or 0xFF. This is why uint8_t x = -1 is a common way to get "all bits set".

18. A float comparison that fails

Why might this condition be false?

float v = 0.1f + 0.2f;
if (v == 0.3f) { /* ... */ }
  1. A.float cannot store any values below 1.0
  2. B.0.1 and 0.2 are not exact in binary floating point
  3. C.The == operator does not work on floats
  4. D.The compiler turns 0.3f into an integer
Show answer

Answer: B. 0.1 and 0.2 are not exact in binary floating point

Most decimal fractions cannot be stored exactly in binary floating point, so tiny rounding errors appear. Compare with a tolerance, such as fabsf(v - 0.3f) < 1e-6f, or use integers or fixed-point for exact values.

19. What MISRA C is for

What is MISRA C?

  1. A.A C compiler made for automotive chips
  2. B.Coding guidelines for safer, more predictable C
  3. C.A real-time operating system for MCUs
  4. D.A file format for firmware images
Show answer

Answer: B. Coding guidelines for safer, more predictable C

MISRA C is a set of rules that restrict risky C features, such as undefined behaviour, unclear conversions and dynamic memory. It started in automotive and is widely used in safety-related firmware, checked with static analysis tools.

20. Value of an uninitialised global

A global "uint32_t error_count;" has no initialiser. What value does it hold when main() starts on a typical bare-metal toolchain?

  1. A.Whatever was left in RAM from before the reset
  2. B.0, because startup code clears the .bss section
  3. C.0xFFFFFFFF, because erased flash reads as all ones
  4. D.It depends on the order of the source files
Show answer

Answer: B. 0, because startup code clears the .bss section

Uninitialised and zero-initialised globals go into .bss, and the C standard requires them to start at zero. The startup code (or C runtime) writes zeros over the whole .bss range before calling main().

Embedded C intermediate interview questions medium

21. Size of a struct

On a typical 32-bit ARM compiler, what is sizeof(struct S)?

struct S {
    uint8_t  a;
    uint32_t b;
    uint8_t  c;
};
  1. A.6
  2. B.8
  3. C.9
  4. D.12
Show answer

Answer: D. 12

b needs 4-byte alignment, so 3 padding bytes follow a. After c, 3 more bytes pad the struct to a multiple of 4: 1 + 3 + 4 + 1 + 3 = 12. Reordering to b, a, c gives 8. Be careful when sending structs over a wire, since padding and endianness vary.

22. Dropping unused functions

A vendor library has many functions you never call, yet they all end up in flash. Which build settings let the linker remove them?

  1. A.-O0 and -g3
  2. B.-ffunction-sections -fdata-sections with the linker flag --gc-sections
  3. C.-Wall -Wextra
  4. D.-fno-inline and -static
Show answer

Answer: B. -ffunction-sections -fdata-sections with the linker flag --gc-sections

By default a whole object file's .text is one section, so the linker must keep all of it. Putting each function and data item in its own section lets --gc-sections discard anything unreferenced. This is a standard size saving in nearly every embedded build.

23. Initialised global reads garbage

After switching to a custom startup file, int threshold = 50; reads a random value at the start of main, while zero-initialised globals look fine. What is wrong?

  1. A.The compiler optimised the variable away
  2. B.The stack is too small
  3. C.The startup code no longer copies the .data section from flash to RAM
  4. D.The variable needs to be volatile
Show answer

Answer: C. The startup code no longer copies the .data section from flash to RAM

Initial values of .data variables are stored in flash, and the reset handler must copy them to RAM before main runs. Zeroing .bss is a separate loop, which explains why zero globals still work. Check the copy loop and the linker symbols it uses, such as _sidata, _sdata and _edata.

24. Extracting a register field

A 32-bit status register has a 3-bit field in bits 6..4. Which expression gives the field value as 0–7?

  1. A.(reg & 0x7u) >> 4
  2. B.(reg >> 6) & 0x3u
  3. C.reg & (0x7u << 4)
  4. D.(reg >> 4) & 0x7u
Show answer

Answer: D. (reg >> 4) & 0x7u

Shift right by the field's lowest bit position so the field lands in bits 2..0, then mask with three 1-bits (0x7). Masking without shifting, as in the last option, keeps the value in place and gives multiples of 16.

25. strncpy and the missing terminator

src is "TEMPERATURE" (11 characters). What is wrong with dst afterwards?

char dst[8];
strncpy(dst, src, sizeof dst);
printf("%s", dst);
  1. A.strncpy writes 11 bytes and overflows dst
  2. B.dst contains "TEMPERA" and a null terminator
  3. C.dst has no null terminator, so printf reads past it
  4. D.strncpy fails and leaves dst empty
Show answer

Answer: C. dst has no null terminator, so printf reads past it

strncpy copies at most n bytes and does not add a terminator if the source is that long or longer. dst holds "TEMPERAT" with no '\0', so printf keeps reading memory. Set dst[sizeof dst - 1] = '\0' or use snprintf.

26. What offsetof gives

What does offsetof(struct pkt, crc) return?

#include <stddef.h>
struct pkt { uint8_t type; uint8_t len; uint16_t crc; uint8_t data[32]; };
  1. A.Byte offset of crc from the struct start
  2. B.The size in bytes of the crc member
  3. C.The address of crc in a global pkt
  4. D.The number of members before crc
Show answer

Answer: A. Byte offset of crc from the struct start

offsetof gives the distance in bytes from the start of the struct to the member, including any padding. Here it is 2, since crc is naturally aligned after two bytes. It is handy for checking a struct matches a wire format.

27. A NULL check after use

What can an optimising compiler do with the if test?

int get_len(struct buf *b) {
    int n = b->len;
    if (b == NULL) return -1;
    return n;
}
  1. A.Move it before the dereference automatically
  2. B.Remove it, since b was already dereferenced
  3. C.Turn it into a runtime fault handler
  4. D.Nothing; NULL checks are never optimised
Show answer

Answer: B. Remove it, since b was already dereferenced

Dereferencing NULL is undefined, so after b->len the compiler may assume b is not NULL and delete the check. Check pointers before using them; tests placed after the use give no protection.

28. A struct over peripheral registers

Vendor headers often define a peripheral like this. What makes it work?

typedef struct {
    volatile uint32_t CR;
    volatile uint32_t SR;
    volatile uint32_t DR;
} UART_TypeDef;
#define UART1 ((UART_TypeDef *)0x40011000u)
  1. A.Members map to consecutive 32-bit register offsets
  2. B.The linker copies the struct into the peripheral at boot
  3. C.The compiler reorders members to match the hardware
  4. D.Each member is a pointer to its register
Show answer

Answer: A. Members map to consecutive 32-bit register offsets

Each uint32_t member is 4 bytes with no padding between them, so CR, SR and DR sit at offsets 0, 4 and 8 from the base address. UART1->SR then reads 0x40011004. Reserved gaps are filled with padding arrays in the struct.

29. What *p++ does

What does the expression *p++ = 0; do?

  1. A.Writes 0 where p points, then moves p forward
  2. B.Increments the value p points to, then writes 0
  3. C.Moves p forward, then writes 0 at the new place
  4. D.Writes 0 to p itself, then increments it
Show answer

Answer: A. Writes 0 where p points, then moves p forward

Postfix ++ binds tighter than unary *, so it is *(p++): the old p is used for the write, and p is advanced. This is the common pattern for filling buffers. (*p)++ would increment the pointed-to value instead.

30. Heap and stack meeting

A bare-metal system places the heap after .bss and the stack at the top of RAM. Random crashes appear after many malloc calls. What is a likely cause?

  1. A.malloc moved .data into flash
  2. B.The stack moved into the flash region
  3. C.The heap grew up into the stack area
  4. D.The vector table was copied into the heap
Show answer

Answer: C. The heap grew up into the stack area

The heap grows up and the stack grows down into the same free gap. Without a check, they can overlap, so stack frames overwrite heap blocks or the reverse. A heap limit in _sbrk, a fixed heap size in the linker script, or no heap at all prevents this.

31. Putting a buffer in a special RAM

An STM32H7 needs a DMA buffer in the D2 SRAM, which the linker script maps to a section called .dma_buf. How is the buffer placed there with GCC?

  1. A.static uint8_t buf[512] __attribute__((section(".dma_buf")));
  2. B.static uint8_t buf[512] __attribute__((aligned(".dma_buf")));
  3. C.static volatile uint8_t buf[512]; /* volatile picks the RAM */
  4. D.#pragma pack(".dma_buf") static uint8_t buf[512];
Show answer

Answer: A. static uint8_t buf[512] __attribute__((section(".dma_buf")));

The section attribute puts the object in a named output section, and the linker script decides which memory that section maps to. Note that a section other than .bss is not zeroed by default startup code unless you add it.

32. The first two vector table words

On reset, what does a Cortex-M core load from the first two 32-bit words of the vector table?

  1. A.The reset handler address, then the NMI handler address
  2. B.The chip ID, then the flash size
  3. C.The clock setting, then the stack pointer
  4. D.The initial stack pointer, then the reset handler address
Show answer

Answer: D. The initial stack pointer, then the reset handler address

Word 0 is loaded into MSP and word 1 into PC, with its Thumb bit set. No code runs before this, which is why C startup code can rely on a valid stack from the very first instruction.

33. What runs before main

In a typical GCC Cortex-M project, what does Reset_Handler do before calling main?

  1. A.Erase flash, then download the program from the debugger
  2. B.Zero all of RAM including the stack, then enable every peripheral
  3. C.Copy .data from flash, zero .bss, then call SystemInit and libc init
  4. D.Load main into RAM and jump to the bootloader
Show answer

Answer: C. Copy .data from flash, zero .bss, then call SystemInit and libc init

The reset handler prepares the C environment: copy initial values for .data, clear .bss, set up clocks (often SystemInit) and run __libc_init_array for constructors. Only then is main called. Skipping any of these breaks C's guarantees about globals.

34. Is returning a literal safe

Is it safe to return this pointer and use it later?

const char *err_name(int e) {
    if (e == 1) return "TIMEOUT";
    return "UNKNOWN";
}
  1. A.No; the literals are on the stack and vanish
  2. B.No; literals exist only during the return statement
  3. C.Yes, but only if the function is declared static
  4. D.Yes; string literals have static storage duration
Show answer

Answer: D. Yes; string literals have static storage duration

String literals are arrays with static storage duration, typically in .rodata in flash, so the pointer stays valid for the whole program. This is different from a local char array, which lives on the stack.

35. A variable-length array

Why is this risky in firmware?

void log_frame(size_t len) {
    uint8_t tmp[len];
    /* ... */
}
  1. A.VLAs are always placed in flash memory
  2. B.Stack use depends on len, with no failure check
  3. C.VLAs are zeroed on entry, which is very slow
  4. D.The array is shared between all callers
Show answer

Answer: B. Stack use depends on len, with no failure check

A VLA takes len bytes of stack at runtime. A large or corrupt len silently overflows the stack, and C gives no way to detect failure. Fixed-size buffers with a length check are safer, and MISRA C forbids VLAs.

36. A struct copy inside an ISR

An ISR does struct frame f = latest_frame; where struct frame is 512 bytes. What two costs does this add?

  1. A.512 bytes of stack and a slow copy in the ISR
  2. B.A heap allocation and a later free
  3. C.A flash write and a cache flush
  4. D.Nothing, because struct copies are free
Show answer

Answer: A. 512 bytes of stack and a slow copy in the ISR

The local copy needs 512 bytes on the interrupt stack (MSP on Cortex-M), and the copy itself takes many cycles, raising interrupt latency. Use a pointer to the data, or swap buffer pointers, instead of copying.

37. char on ARM and x86

This code works in a PC unit test but the error branch never runs on an ARM Cortex-M build with GCC. Why?

char c = uart_getc();     /* returns 0xFF on error */
if (c == -1) handle_error();
  1. A.ARM cannot compare chars with negative numbers
  2. B.uart_getc returns int, which is always positive
  3. C.The ARM compiler removes all error handlers
  4. D.Plain char is unsigned on ARM, so c is 255, not -1
Show answer

Answer: D. Plain char is unsigned on ARM, so c is 255, not -1

Whether plain char is signed is implementation-defined: it is signed on x86 and unsigned by default on ARM. On ARM c holds 255, which never equals −1. Use int for values that can be −1, or uint8_t or int8_t explicitly.

38. A timeout that survives wraparound

A 32-bit millisecond tick wraps after about 49.7 days. Which timeout check keeps working across the wrap?

  1. A.if ((uint32_t)now >= start + timeout)
  2. B.if ((uint32_t)(now - start) >= timeout)
  3. C.if (now - timeout > start)
  4. D.if (start + now >= timeout)
Show answer

Answer: B. if ((uint32_t)(now - start) >= timeout)

Unsigned subtraction wraps modulo 2^32, so now − start gives the true elapsed time even if now has wrapped past zero. start + timeout can wrap to a small number, making now >= start + timeout true too early.

39. i = i++ and similar

What does the C standard say about this line?

idx = idx++ + 1;
  1. A.idx always increases by exactly 2
  2. B.idx always increases by exactly 1
  3. C.Undefined: idx is modified twice unsequenced
  4. D.It is a compile error in C99 and later
Show answer

Answer: C. Undefined: idx is modified twice unsequenced

idx is written by ++ and by = with no sequence point between them, which is undefined behaviour. Different compilers or optimisation levels can give different results. Write idx += 2; or split it into separate statements.

40. Checking a struct size at build time

A struct must exactly match a 16-byte flash record. How can the build fail if someone adds a field?

  1. A._Static_assert(sizeof(struct rec) == 16, "rec size");
  2. B.assert(sizeof(struct rec) == 16); in main
  3. C.#if sizeof(struct rec) != 16 / #error / #endif
  4. D.volatile struct rec r = { 16 };
Show answer

Answer: A. _Static_assert(sizeof(struct rec) == 16, "rec size");

_Static_assert (static_assert from <assert.h>) is checked by the compiler, so a wrong size stops the build. assert runs only at runtime, and the preprocessor cannot evaluate sizeof, so the #if version does not work.

Embedded C interview questions for experienced candidates hard

41. Corruption in one task

Variables in one FreeRTOS task occasionally get corrupted after adding a feature that uses a large local array. What is the most likely cause?

  1. A.The task's stack overflows into neighbouring memory; increase its stack, move the array to static storage, and enable stack overflow checking
  2. B.The flash memory is wearing out
  3. C.The compiler optimisation level is too low
  4. D.The RTOS tick rate is too slow
Show answer

Answer: A. The task's stack overflows into neighbouring memory; increase its stack, move the array to static storage, and enable stack overflow checking

Each task has a fixed-size stack. A large local array can exceed it and silently overwrite other data. FreeRTOS configCHECK_FOR_STACK_OVERFLOW, uxTaskGetStackHighWaterMark and the MPU help detect it.

42. DMA data looks stale

On a Cortex-M7 with data cache enabled, the CPU reads old values from a buffer that DMA just filled from the ADC. What is the fix?

  1. A.Use a slower DMA clock
  2. B.Declare the buffer as const
  3. C.Increase the DMA priority
  4. D.Invalidate the cache for that buffer before reading (cache-line aligned), or place DMA buffers in a non-cacheable region via the MPU
Show answer

Answer: D. Invalidate the cache for that buffer before reading (cache-line aligned), or place DMA buffers in a non-cacheable region via the MPU

DMA writes directly to RAM, but the CPU may read stale data from its cache. Cache maintenance (SCB_InvalidateDCache_by_Addr after DMA receive, clean before DMA transmit) or non-cacheable memory keeps both views consistent. Buffers should be aligned to the 32-byte cache line.

43. What restrict promises

What does restrict tell the compiler, and what happens if the caller breaks the promise?

void mix(int16_t *restrict out, const int16_t *restrict a,
         const int16_t *restrict b, size_t n);
  1. A.The buffers do not overlap; if they do, behaviour is undefined
  2. B.The buffers are read-only; writes are rejected at runtime
  3. C.The buffers are in RAM; flash pointers are rejected
  4. D.The compiler checks overlap and falls back safely
Show answer

Answer: A. The buffers do not overlap; if they do, behaviour is undefined

restrict promises that, during the call, each object is accessed only through that pointer. The compiler can then keep values in registers and vectorise without reloading after each store. Passing overlapping buffers breaks the promise and is undefined, with no runtime check.

44. An array exactly the string length

What does this definition contain, and is it valid C?

char id[5] = "HELLO";
  1. A.A compile error, as six bytes are needed
  2. B.Valid; the terminator overwrites the next variable
  3. C.Valid; five chars and no null terminator
  4. D.Valid; the compiler grows the array to six
Show answer

Answer: C. Valid; five chars and no null terminator

C allows a char array initialised from a literal of exactly its size, and the terminator is simply dropped (C++ rejects this). id is not a string, so strlen or printf("%s") will read past it. Use char id[] = "HELLO"; to get six bytes.

45. Recovering the parent struct

A list stores pointers to struct node embedded inside struct timer. Given struct node *n, how does container_of get the timer?

struct node { struct node *next; };
struct timer { uint32_t period; struct node link; };
#define container_of(p, T, m) ((T *)((char *)(p) - offsetof(T, m)))
struct timer *t = container_of(n, struct timer, link);
  1. A.It subtracts link's offset from n's address
  2. B.It searches all timers for a matching node
  3. C.It reads a hidden back-pointer in node
  4. D.It adds the size of struct timer to n
Show answer

Answer: A. It subtracts link's offset from n's address

link sits at a fixed offset inside struct timer. Subtracting that offset from the node's address gives the start of the timer. The cast to char * makes the subtraction work in bytes. This intrusive pattern avoids separate allocations for list nodes.

46. Writing to a const table

A calibration table is declared static const and placed in flash. Code casts away const and writes to it. What happens?

static const uint16_t cal[4] = { 100, 200, 300, 400 };
((uint16_t *)cal)[1] = 250;
  1. A.cal[1] becomes 250 in flash
  2. B.The table is copied to RAM and changed there
  3. C.The compiler rejects the cast
  4. D.Undefined; the write may fault or be ignored
Show answer

Answer: D. Undefined; the write may fault or be ignored

Modifying an object defined as const is undefined behaviour even if you cast the qualifier away. The table sits in flash, so a plain store usually raises a bus fault or does nothing, and the compiler may already have folded the old value into code.

47. Aligning a DMA buffer

A DMA engine on a Cortex-M7 needs buffers aligned to the 32-byte cache line. Which declaration gives that in standard C11?

  1. A.static uint8_t buf[256] __attribute__((packed));
  2. B.static volatile uint8_t buf[256 + 32];
  3. C.alignas(32) static uint8_t buf[256];
  4. D.static uint8_t buf[256] = {32};
Show answer

Answer: C. alignas(32) static uint8_t buf[256];

alignas (from <stdalign.h>, or _Alignas) sets the minimum alignment. Matching the cache line and rounding the size to whole lines lets you clean and invalidate the cache for the buffer without touching neighbouring data. packed lowers alignment, which is the opposite.

48. sizeof a member through a null pointer

Is this macro safe, given that it appears to dereference a null pointer?

#define MEMBER_SIZE(T, m) sizeof(((T *)0)->m)
uint8_t n = MEMBER_SIZE(struct pkt, data);
  1. A.No; it reads address 0 at runtime
  2. B.No; it is a compile error in C
  3. C.Yes, but only at -O0
  4. D.Yes; sizeof does not evaluate its operand
Show answer

Answer: D. Yes; sizeof does not evaluate its operand

Except for variable-length arrays, sizeof only looks at the type of its operand and never evaluates it, so no memory access happens. The result is a compile-time constant. This is a common way to get a member size without an object.

49. Reading arm-none-eabi-size

arm-none-eabi-size prints this for the firmware. How much flash and how much static RAM does it use?

   text    data     bss     dec     hex filename
  48200     600    9400   58200    e358 app.elf
  1. A.Flash 48,200 bytes; RAM 9,400 bytes
  2. B.Flash 58,200 bytes; RAM 58,200 bytes
  3. C.Flash 48,200 bytes; RAM 10,000 bytes
  4. D.Flash 48,800 bytes; RAM 10,000 bytes
Show answer

Answer: D. Flash 48,800 bytes; RAM 10,000 bytes

Flash holds code and constants (text) plus the initial values of .data, so 48,200 + 600 = 48,800. RAM holds .data and .bss: 600 + 9,400 = 10,000. Stack and heap are on top of that unless they are reserved as .bss-like sections.

50. One table, flash and RAM

A developer adds uint16_t lut[1024] = { /* values */ }; and both flash and RAM use rise by 2 KB. What change saves the RAM, if the table never changes?

  1. A.Declare it static so it moves to .bss
  2. B.Declare it const so it stays in .rodata in flash
  3. C.Declare it volatile so it is read from flash
  4. D.Move the definition into a header file
Show answer

Answer: B. Declare it const so it stays in .rodata in flash

An initialised writable array lives in .data: its values are stored in flash and copied to RAM at boot, so it costs both. A const table goes to .rodata and is read directly from flash on Cortex-M, using no RAM.

51. Catching stack overflow at once

A team wants an immediate fault the moment a task stack overflows, rather than silent corruption. What Cortex-M feature is commonly used?

  1. A.The SysTick timer reloaded each context switch
  2. B.The NVIC priority grouping register
  3. C.An MPU no-access region just below each stack
  4. D.The ITM trace port with printf output
Show answer

Answer: C. An MPU no-access region just below each stack

A small MPU region with no access placed at the stack limit turns any write past the end into a MemManage fault at the bad instruction. ARMv8-M also offers stack limit registers (PSPLIM, MSPLIM). Canary checks only notice the damage later.

52. A string table that uses RAM

This table of menu strings uses RAM for the pointer array. Which declaration puts both the strings and the pointer array in flash?

const char *menu[] = { "Start", "Stop", "Setup" };
  1. A.char *const menu[] = { ... };
  2. B.const char menu[] = { ... };
  3. C.const char *const menu[] = { ... };
  4. D.static const char *menu[] = { ... };
Show answer

Answer: C. const char *const menu[] = { ... };

In const char *menu[], the strings are const but the pointers are writable, so the array goes in .data. Adding const after the * makes the pointers themselves const, letting the whole table live in .rodata. static alone only changes visibility.

53. An odd function address

The map file shows uart_isr at 0x08000420, but printing (uint32_t)&uart_isr gives 0x08000421. Why?

  1. A.The compiler adds 1 to skip a padding byte
  2. B.The function is misaligned by one byte
  3. C.The value includes a checksum bit
  4. D.Bit 0 marks Thumb state for Cortex-M branches
Show answer

Answer: D. Bit 0 marks Thumb state for Cortex-M branches

Cortex-M runs only Thumb code, and interworking branches use bit 0 of the target address to select Thumb state. Function pointers and vector table entries therefore have bit 0 set. Branching to an even address raises a UsageFault (INVSTATE).

54. Shifting by 32

A mask helper is called with n = 32. What happens on a 32-bit target?

uint32_t mask_n(unsigned n) { return (1u << n) - 1u; }
  1. A.Undefined; on ARM it may return 0xFFFFFFFF, on x86 0
  2. B.It always returns 0xFFFFFFFF as intended
  3. C.It always returns 0, which is defined
  4. D.It is a compile error for any n
Show answer

Answer: A. Undefined; on ARM it may return 0xFFFFFFFF, on x86 0

Shifting by a count greater than or equal to the type width is undefined. Hardware differs: x86 masks the count to 5 bits (1u << 32 acts as 1u << 0), while ARM register shifts yield 0, giving different results. Handle n == 32 separately.

55. One million on an 8-bit MCU

On an AVR (16-bit int), why does this not give 1,000,000?

uint32_t us = 1000 * 1000;
  1. A.It is a 16-bit int multiply; write 1000UL * 1000
  2. B.uint32_t only holds 16 bits on AVR
  3. C.The compiler rounds the result to 65,535
  4. D.Constants are stored as floats on AVR targets
Show answer

Answer: A. It is a 16-bit int multiply; write 1000UL * 1000

Both literals are int, so the product is computed as a 16-bit int before being stored. 1,000,000 does not fit, so it overflows (undefined; compilers warn). Making one operand long, as 1000UL, makes the whole multiply 32-bit.

56. Flag cleared as the last ISR line

On a Cortex-M4, a timer ISR clears the peripheral flag as its very last instruction. The ISR sometimes runs a second time for one event. Why?

  1. A.The NVIC always runs each ISR twice by design
  2. B.The flag write is still in flight on the bus when the ISR exits
  3. C.Clearing flags must be done before entering the ISR
  4. D.The timer counts twice as fast inside an ISR
Show answer

Answer: B. The flag write is still in flight on the bus when the ISR exits

Peripheral writes go through buffers and slower buses, so the interrupt request can still be active when the exception returns, and the NVIC takes it again. Clear the flag early in the ISR, or read the register back or use __DSB() after clearing so the write completes first.

57. A ready flag that is always false

Bit 8 of STATUS is set, yet ready is 0. Why, and what fixes it?

uint8_t ready = STATUS & (1u << 8);
  1. A.0x100 is cut to 8 bits, giving 0; use != 0 to get 1
  2. B.Bit 8 cannot be read from a 32-bit register
  3. C.The & operator returns only bit 0
  4. D.STATUS must be declared const first
Show answer

Answer: A. 0x100 is cut to 8 bits, giving 0; use != 0 to get 1

STATUS & 0x100 gives 0x100 when the bit is set. Storing that in a uint8_t keeps only the low 8 bits, which are 0. Writing (STATUS & (1u << 8)) != 0, or using bool, turns any nonzero result into 1.

58. Converting 300.0 to a byte

A PWM duty is computed as a float that can reach 300.0, then stored with (uint8_t)duty. What does C say?

  1. A.It wraps to 44, as for integer conversions
  2. B.It saturates to 255 on every compiler
  3. C.Undefined: the value does not fit, so clamp first
  4. D.It is a compile error to cast floats to uint8_t
Show answer

Answer: C. Undefined: the value does not fit, so clamp first

Converting a floating value to an integer type whose range cannot hold the truncated value is undefined, unlike integer to unsigned conversion, which wraps. Different FPUs and compilers give different results. Clamp to 0–255 before converting.

59. Dividing by zero on Cortex-M3

On a Cortex-M3, x / 0 with unsigned ints returns 0 instead of crashing. What is happening, and how can it be caught?

  1. A.The compiler replaces every division with 0
  2. B.Division by zero is defined as 0 in C
  3. C.UDIV returns 0 by default; set DIV_0_TRP to trap it
  4. D.The FPU handles it and returns infinity
Show answer

Answer: C. UDIV returns 0 by default; set DIV_0_TRP to trap it

Division by zero is undefined in C. On ARMv7-M, the hardware divide instructions return 0 unless the DIV_0_TRP bit in SCB->CCR is set, which makes them raise a UsageFault. Enabling the trap during development exposes these bugs.

60. C++ globals never constructed

A project moved to C++. A global object's constructor sets up a driver, but the driver is never initialised and members read zero. The custom startup file was written for C. What is missing?

  1. A.A call to __libc_init_array before main()
  2. B.The extern "C" keyword on the main() function
  3. C.Compiling every file with -fno-exceptions
  4. D.Placing the global object in .noinit
Show answer

Answer: A. A call to __libc_init_array before main()

Constructors of global C++ objects are listed in .init_array, and the C runtime runs them via __libc_init_array (or equivalent) before main(). A minimal C startup that only copies .data and zeroes .bss skips this, so objects keep their zeroed memory. Also avoid depending on the order of global constructors across files.

Ready to test yourself?

Take a 15-question Embedded C MCQ test with a score at the end, or practise the full Embedded C question bank and earn points on the leaderboard.