All jobs / Embedded / hardware
C / C++ interview questions
C / C++ questions a Embedded / hardware can be asked, from first job to senior. Open one to see the strongest answer and why it works.
A status register never seems to change in your loop, but the scope says it does. First suspect?
The strongest answer: The compiler cached the read — I mark MMIO volatile (or use the vendor accessor) so each access hits the hardware
Why it works: Exactly. MMIO is not a RAM variable. Volatile or the accessor is the contract.
- Treat MMIO as volatile.
- Do not rely on printf to “refresh” a register.
- Do not ban optimisation as the design.
- The scope can be right when C looks stuck.
Why might you mark a lookup table `const` on a microcontroller?
The strongest answer: So it can live in flash, not eat RAM — and so I do not write it by accident
Why it works: Exactly. RAM is the scarce one. const is a placement and a promise.
- const tables can live in flash.
- Saves RAM; blocks writes.
- Not merely a linter toy.
- Do not leave placement to vibes.
What does `static` mean on a file-scope variable, on a local variable, and on a function?
The strongest answer: At file scope it hides the symbol from other translation units; on a local it keeps the value between calls; on a function it stops the name being linked from anywhere else
Why it works: Exactly — one keyword, two jobs: linkage at file scope, lifetime inside a function. Knowing which one applies saves a lot of confusion.
- Static means lifetime or linkage, never immutability.
- File scope: hidden from other files.
- Local: value survives the call.
- Static state is shared state — mind ISRs.
You need bit 3 of a register. Strongest habit?
The strongest answer: A named mask and a read-modify-write I can show — not a magic `|= 8` I cannot explain next month
Why it works: Exactly. Names and RMW. Flooding a register is how you start a motor in a debug session.
- Named masks + careful RMW.
- Do not write all-ones.
- Do not toggle to discover.
- Do not guess the header.
Dereferencing NULL on many MCUs does what?
The strongest answer: Often a fault — or a wrap to address zero if you were sloppy — I check pointers and I do not treat NULL as a cute unused value
Why it works: Exactly. No Java. Fault or trash at zero. Swallowing faults is a product defect.
- NULL is a fault or a zero-page smash.
- Check pointers.
- Bare metal ≠ safe deref.
- Do not swallow faults as 0.
The team ignores 400 compiler warnings. Your line?
The strongest answer: We turn on -Wall -Werror (or a written exception list) — a warning is a bug we have not read
Why it works: Exactly. Read or except. Tree-wide pragmas are how you miss a real one.
- Treat warnings as unread bugs.
- Werror or a written exception.
- No tree-wide hide.
- Linker-clean ≠ safe.
In C++ firmware, why would you prefer a `constexpr` constant over a `#define`?
The strongest answer: constexpr has a type and obeys scope, so the compiler checks it and the debugger can show it; a macro is blind text substitution that ignores scope and leaves nothing to inspect
Why it works: Right — a typed, scoped constant is one the compiler can check and the debugger can print. A macro does neither.
- Prefer a typed constant to text substitution.
- constexpr is type checked and scoped.
- The debugger can show it; macros vanish.
- If you must macro, parenthesise everything.
A struct is cast straight onto a UART receive buffer, and the fields come out shifted on one of the two boards.
What is going on, and what would you do instead?
The strongest answer: The compiler pads members up to their alignment and the two builds pad differently. I serialise field by field into a byte buffer with a fixed order instead of casting a struct
Why it works: Exactly — a wire format is a byte layout you define, not whatever your compiler happened to lay out today.
- Define the wire format; never cast a struct onto it.
- Padding is per compiler and target.
- Serialise field by field, fixed order.
- Packed still leaves alignment and endianness.
How do you use a coding standard without worship?
The strongest answer: A written deviation with a reason, and I fix the ones that hide undefined behaviour — I do not #pragma the tree to “go green”
Why it works: Exactly. Read, deviate on purpose. Tree pragmas and box badges are theatre.
- Fix UB-hiding issues.
- Written deviations.
- No tree-wide pragma green.
- No box badge as proof.
When do you allow C++ on the MCU, and what do you ban?
The strongest answer: A written subset: no throwing across the RTOS, no hidden heap in ctors I cannot see — I do not say “modern C++” as a plan
Why it works: Exactly. A subset you can debug at 02:00. Desktop exceptions and macro objects are two cliffs.
- Written C++ subset.
- No hidden heap or throws across the kernel.
- “Modern” is not a plan.
- Do not fragment dialects per app.