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?
- A.The task's stack overflows into neighbouring memory; increase its stack, move the array to static storage, and enable stack overflow checking
- B.The flash memory is wearing out
- C.The compiler optimisation level is too low
- 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?
- A.Use a slower DMA clock
- B.Declare the buffer as const
- C.Increase the DMA priority
- 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);- A.The buffers do not overlap; if they do, behaviour is undefined
- B.The buffers are read-only; writes are rejected at runtime
- C.The buffers are in RAM; flash pointers are rejected
- 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";
- A.A compile error, as six bytes are needed
- B.Valid; the terminator overwrites the next variable
- C.Valid; five chars and no null terminator
- 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);- A.It subtracts link's offset from n's address
- B.It searches all timers for a matching node
- C.It reads a hidden back-pointer in node
- 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;- A.cal[1] becomes 250 in flash
- B.The table is copied to RAM and changed there
- C.The compiler rejects the cast
- 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?
- A.static uint8_t buf[256] __attribute__((packed));
- B.static volatile uint8_t buf[256 + 32];
- C.alignas(32) static uint8_t buf[256];
- 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);
- A.No; it reads address 0 at runtime
- B.No; it is a compile error in C
- C.Yes, but only at -O0
- 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
- A.Flash 48,200 bytes; RAM 9,400 bytes
- B.Flash 58,200 bytes; RAM 58,200 bytes
- C.Flash 48,200 bytes; RAM 10,000 bytes
- 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?
- A.Declare it static so it moves to .bss
- B.Declare it const so it stays in .rodata in flash
- C.Declare it volatile so it is read from flash
- 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?
- A.The SysTick timer reloaded each context switch
- B.The NVIC priority grouping register
- C.An MPU no-access region just below each stack
- 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" };- A.char *const menu[] = { ... };
- B.const char menu[] = { ... };
- C.const char *const menu[] = { ... };
- 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?
- A.The compiler adds 1 to skip a padding byte
- B.The function is misaligned by one byte
- C.The value includes a checksum bit
- 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; }- A.Undefined; on ARM it may return 0xFFFFFFFF, on x86 0
- B.It always returns 0xFFFFFFFF as intended
- C.It always returns 0, which is defined
- 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;
- A.It is a 16-bit int multiply; write 1000UL * 1000
- B.uint32_t only holds 16 bits on AVR
- C.The compiler rounds the result to 65,535
- 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?
- A.The NVIC always runs each ISR twice by design
- B.The flag write is still in flight on the bus when the ISR exits
- C.Clearing flags must be done before entering the ISR
- 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);
- A.0x100 is cut to 8 bits, giving 0; use != 0 to get 1
- B.Bit 8 cannot be read from a 32-bit register
- C.The & operator returns only bit 0
- 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?
- A.It wraps to 44, as for integer conversions
- B.It saturates to 255 on every compiler
- C.Undefined: the value does not fit, so clamp first
- 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?
- A.The compiler replaces every division with 0
- B.Division by zero is defined as 0 in C
- C.UDIV returns 0 by default; set DIV_0_TRP to trap it
- 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?
- A.A call to __libc_init_array before main()
- B.The extern "C" keyword on the main() function
- C.Compiling every file with -fno-exceptions
- 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.