1
What is the difference between a pointer and a reference in C?
Answer
C does not have references - references are a C++ feature. In C, pointers are variables that store memory addresses. A pointer can be NULL, can be reassigned to point to different variables, supports pointer arithmetic, and must be explicitly dereferenced with the * operator. In C++, references must be initialized, cannot be NULL, cannot be reseated, and are automatically dereferenced.
2
Explain the difference between malloc(), calloc(), and realloc().
Answer
malloc(size) allocates a block of 'size' bytes of uninitialized memory. calloc(num, size) allocates memory for 'num' elements of 'size' bytes each and initializes all bytes to zero. realloc(ptr, new_size) resizes a previously allocated block to 'new_size', preserving existing data. All return void* on success or NULL on failure. In embedded systems, dynamic allocation is often avoided due to fragmentation and determinism issues.
3
What is a memory-mapped I/O and how is it implemented in C?
Answer
Memory-mapped I/O maps peripheral registers to specific memory addresses. In C, you access them using volatile pointers: volatile uint32_t *reg = (volatile uint32_t *)0x40021000; *reg = 0x01; The volatile keyword prevents the compiler from optimizing away reads/writes, ensuring every access actually reaches the hardware. This is the standard way to access GPIO, UART, SPI, and other peripherals in microcontrollers.
4
Why is the volatile keyword critical in embedded C?
Answer
volatile tells the compiler that a variable's value can change unexpectedly - by hardware, interrupts, or other threads. Without volatile, the compiler may cache register values in CPU registers, reorder accesses, or optimize away 'redundant' reads. All hardware registers, ISR-shared variables, and memory-mapped I/O must be declared volatile. Failing to use volatile causes bugs that appear only with optimization enabled.
5
What is the difference between const and volatile? Can a variable be both?
Answer
const means the program should not modify the variable - the compiler enforces this. volatile means the value can change unexpectedly, so the compiler must not optimize accesses. A variable can be both: 'volatile const uint32_t *status_reg' means the register is read-only from the software's perspective but can change due to hardware. This is common for status registers.
6
Explain pointer arithmetic with arrays in C.
Answer
When you add an integer n to a pointer p of type T*, the pointer advances by n * sizeof(T) bytes. For example, if int *p points to an array, p+1 points to the next int (4 bytes later on 32-bit). Array subscript a[i] is equivalent to *(a + i). This is why pointer arithmetic respects the type size. In embedded C, pointer arithmetic on void* is not standard C and should be avoided.
7
What is a function pointer and where is it used in embedded systems?
Answer
A function pointer stores the address of a function: void (*fp)(int) = &myFunc; fp(42); In embedded systems, function pointers are used for: callback mechanisms (ISR registration), state machines (function pointer tables), command dispatchers (UART protocol handlers), plugin/driver architectures (function pointer structs for HAL), and jump tables for bootloader-to-application transitions.
8
What is the difference between stack and heap memory?
Answer
Stack memory is automatically managed, grows/shrinks with function calls, stores local variables and return addresses, and has a fixed size. Heap memory is manually managed (malloc/free), stores dynamically allocated data, and can fragment over time. In embedded systems, stack size is limited (often 1-8KB per task) and heap usage is discouraged or forbidden due to fragmentation and non-deterministic allocation time.
9
What causes a stack overflow in embedded systems and how do you detect it?
Answer
Stack overflow occurs when function calls, local variables, or ISR nesting exceed the allocated stack size. Causes include deep recursion, large local arrays, and nested interrupts. Detection methods: stack painting (fill with pattern, check watermark), MPU/MMU stack guard regions, compiler instrumentation (-fstack-protector), and RTOS stack analysis APIs. Stack overflow corrupts adjacent memory, causing unpredictable crashes.
10
Explain the memory layout of a typical embedded C program.
Answer
From low to high addresses: .text (code, flash), .rodata (constants, flash), .data (initialized globals, copied from flash to RAM at startup), .bss (uninitialized globals, zeroed at startup in RAM), heap (grows upward), and stack (grows downward from top of RAM). The startup code (crt0) copies .data from flash to RAM and zeros .bss before calling main().
11
What is a dangling pointer?
Answer
A dangling pointer points to memory that has been freed or is out of scope. Accessing it causes undefined behavior - data corruption, crashes, or seemingly working code that fails later. Common causes: using a pointer after free(), returning a pointer to a local variable, and ISR accessing a stack variable after the function returns. Prevention: set pointers to NULL after free, use static analysis tools.
12
How does the linker script affect memory layout in embedded C?
Answer
The linker script (*.ld) defines memory regions (flash, RAM, peripherals), section placement (.text in flash, .data in RAM), and startup values (stack pointer, entry point). It controls where code and data are placed, defines symbols like _etext, _sdata, _edata used by startup code, and can create custom sections for DMA buffers or calibration data at specific addresses.
13
How do you set, clear, and toggle specific bits in a register?
Answer
Set bit n: reg |= (1U << n); Clear bit n: reg &= ~(1U << n); Toggle bit n: reg ^= (1U << n); Read bit n: if (reg & (1U << n)). Always use unsigned literals (1U) to avoid undefined behavior with signed left shifts. For multi-bit fields: set: reg |= (value << pos); clear: reg &= ~(mask << pos); write field: reg = (reg & ~(mask << pos)) | (value << pos).
14
What is the purpose of bit fields in C structures?
Answer
Bit fields allow packing multiple fields into a single word: struct { uint8_t flag : 1; uint8_t mode : 3; uint8_t channel : 4; }; They save memory for tightly packed data. However, bit field layout (order, padding) is implementation-defined, making them non-portable across compilers and architectures. For portable hardware register access, explicit bit masking is preferred over bit fields.
15
How do you check if a number is a power of 2 using bit manipulation?
Answer
A power of 2 has exactly one bit set: (n > 0) && ((n & (n - 1)) == 0). This works because n-1 flips all bits below the highest set bit: 8 (1000) & 7 (0111) = 0. Non-powers fail: 6 (0110) & 5 (0101) = 4 ≠ 0. This is commonly used in embedded systems to validate buffer sizes, alignment requirements, and DMA transfer sizes.
16
What is the difference between logical and bitwise operators?
Answer
&& and || are logical operators - they evaluate truthiness and short-circuit. & and | are bitwise operators - they operate on each bit independently. Example: 0x05 && 0x02 = 1 (true), 0x05 & 0x02 = 0x00. Confusing them is a common bug: 'if (flags & FLAG_A && flags & FLAG_B)' needs parentheses due to precedence: 'if ((flags & FLAG_A) && (flags & FLAG_B))'.
17
How do you implement a circular buffer using bit masking?
Answer
For power-of-2 sized buffers, use: index = (index + 1) & (SIZE - 1); instead of modulo. This wraps the index automatically using bit masking. Example with SIZE=256: index & 0xFF masks to 0-255. This is faster than modulo division on most microcontrollers. The buffer struct typically contains: uint8_t buf[SIZE]; volatile uint16_t head; volatile uint16_t tail; with volatile for ISR safety.
18
Explain byte swapping and endianness conversion in embedded C.
Answer
Endianness defines byte order: little-endian (LSB first, x86/ARM) vs big-endian (MSB first, network/CAN). Swap 32-bit: ((x>>24)&0xFF) | ((x>>8)&0xFF00) | ((x<<8)&0xFF0000) | ((x<<24)&0xFF000000). Use compiler builtins like __builtin_bswap32() when available. Automotive protocols often use big-endian (CAN signals), requiring conversion on little-endian ARM MCUs.
19
How do you count the number of set bits in an integer?
Answer
Simple loop: count=0; while(n) { count += n&1; n >>= 1; }. Brian Kernighan's trick: count=0; while(n) { n &= (n-1); count++; }. This clears the lowest set bit each iteration, running in O(set bits) time. Many ARM Cortex-M have a __builtin_popcount() intrinsic. Lookup tables (256-entry for byte-wise) offer constant-time at the cost of ROM.
20
What are bit-banding regions in ARM Cortex-M microcontrollers?
Answer
Bit-banding maps each bit in a memory region to a full 32-bit word in an alias region. Writing 0 or 1 to the alias word atomically sets/clears the corresponding bit without read-modify-write. This eliminates race conditions for shared register access. Formula: alias_addr = base + (byte_offset * 32) + (bit_number * 4). Available in SRAM and peripheral regions.
21
How would you implement a CRC calculation in embedded C?
Answer
Table-based CRC is most common: precompute a 256-entry lookup table for the polynomial, then for each byte: crc = table[(crc ^ byte) & 0xFF] ^ (crc >> 8). CRC-32 uses polynomial 0xEDB88320 (reflected). For memory-constrained systems, a nibble-based (16-entry) table saves ROM. CRC is used for flash integrity, communication verification, and AUTOSAR NvM block protection.
22
What is the significance of alignment in embedded C?
Answer
Alignment means placing data at addresses that are multiples of the data size (4-byte int at 0x4, 0x8, etc.). Misaligned access causes a HardFault on Cortex-M0/M3 or performance penalty on M4/M7. Use __attribute__((aligned(N))) or #pragma pack. DMA transfers, hardware registers, and communication buffers often require specific alignment. The compiler adds padding to structs for alignment.
23
What is MISRA-C and why is it important for automotive?
Answer
MISRA-C is a set of coding guidelines for safety-critical C programming, developed by the Motor Industry Software Reliability Association. It defines rules that prevent undefined behavior, implementation-defined behavior, and common error-prone patterns. MISRA-C:2012 has 143 rules and 16 directives classified as Mandatory, Required, or Advisory. Compliance is required by ISO 26262 for automotive software.
24
Give examples of common MISRA-C violations.
Answer
Common violations include: implicit type conversions (Rule 10.x), using 'int' instead of sized types (Dir 4.6), missing braces on if/else (Rule 15.6), multiple return statements (Rule 15.5), side effects in assertions/sizeof (Rule 13.x), pointer arithmetic on non-array (Rule 18.4), recursion (Rule 17.2), and dynamic memory allocation (Rule 21.3). Each has specific safety rationale.
25
What is the difference between a MISRA Required rule and a Mandatory rule?
Answer
Mandatory rules cannot be deviated under any circumstances - they address critical undefined behavior. Required rules should be followed but can be formally deviated with documented justification and approval. Advisory rules are recommendations that don't require formal deviation. MISRA-C:2012 has 10 mandatory rules, 101 required rules, and 32 advisory rules plus directives.
26
How does MISRA-C handle type conversions?
Answer
MISRA-C requires explicit casts for all narrowing and signed-to-unsigned conversions. It defines 'essential type' categories: boolean, signed, unsigned, floating, character. Implicit conversions between categories are prohibited. The goal is to prevent silent data loss, unexpected sign extension, and implementation-defined behavior. Example: uint8_t x = (uint8_t)(a + b); instead of uint8_t x = a + b;
27
Why does MISRA-C prohibit dynamic memory allocation?
Answer
Rule 21.3 forbids malloc, calloc, realloc, and free because: heap fragmentation can cause allocation failures in long-running systems, allocation time is non-deterministic (violating real-time constraints), memory leaks are difficult to detect in embedded systems, and the heap lacks protection against overflow. All memory should be statically allocated at compile time.
28
What does MISRA-C say about recursion?
Answer
Rule 17.2 prohibits direct and indirect recursion because: recursive functions have unpredictable stack usage that cannot be statically analyzed, stack overflow can crash safety-critical systems, and worst-case execution time becomes non-deterministic. All algorithms must be implemented iteratively. Static analysis tools verify absence of recursion through call-graph analysis.
29
How do you handle a necessary deviation from MISRA-C?
Answer
A formal deviation requires: identifying the specific rule, documenting the technical justification for why the rule cannot be followed, performing a risk assessment of the deviation, obtaining approval from a qualified authority, and recording the deviation in the project's deviation database. The deviation is linked to the specific code location and reviewed during audits.
30
What are the MISRA-C rules regarding pointer usage?
Answer
MISRA-C heavily restricts pointers: no pointer arithmetic except on arrays (18.4), no more than 2 levels of indirection (18.5), no pointer-to-function casts (11.1), restrict pointer conversions between types (11.3-11.8), no use of pointer subtraction except on same array (18.2). These prevent common memory corruption bugs and undefined behavior.
31
What is Polyspace and how is it used for MISRA compliance?
Answer
Polyspace is MathWorks' static analysis tool suite. Bug Finder detects coding defects and MISRA violations through pattern-based analysis. Code Prover uses abstract interpretation to mathematically prove absence of runtime errors (divide by zero, overflow, null pointer) for every possible input - marking code as Green (safe), Red (error), Orange (unproven), or Gray (dead code).
32
What are the rules for using 'goto' in MISRA-C?
Answer
MISRA-C:2012 Rule 15.1 states that 'goto' should not be used. If deviated, the label must be declared later in the same function (no backward jumps, Rule 15.2), and the jump must not enter a nested block (Rule 15.3). In practice, goto is sometimes permitted for error cleanup in a single forward-exit pattern, similar to C's lack of exceptions.
33
How does MISRA-C address undefined and implementation-defined behavior?
Answer
MISRA-C systematically eliminates undefined behavior (UB) through rules like: no signed integer overflow (Rule 12.x), no accessing freed memory, no data races on shared variables. For implementation-defined behavior, MISRA requires documenting all compiler-specific choices (Dir 1.1) and using only portable constructs. The appendix lists all UB items from the C standard.
34
What is the CERT-C standard and how does it relate to MISRA-C?
Answer
CERT-C (SEI CERT C Coding Standard) focuses on security-related coding rules for C. While MISRA-C targets safety, CERT-C targets security: buffer overflow prevention, integer overflow checking, input validation, and secure library usage. ISO/SAE 21434 references CERT-C for automotive cybersecurity. Many rules overlap, and tools like Polyspace and QAC can check both simultaneously.
35
What is an ISR (Interrupt Service Routine)?
Answer
An ISR is a function that executes in response to a hardware interrupt. When an interrupt fires, the CPU saves context (registers, PC) and jumps to the ISR address from the vector table. ISRs should be short, use only volatile-qualified shared variables, avoid blocking calls, and minimize stack usage. In ARM Cortex-M, ISRs are regular C functions specified in the vector table.
36
How do you safely share data between an ISR and the main loop?
Answer
Use volatile for shared variables: volatile uint8_t flag; Use atomic operations or critical sections for multi-byte data: __disable_irq(); shared_data = new_value; __enable_irq(); For larger data, use double-buffering or ring buffers with volatile head/tail pointers. Never use mutex/semaphore from ISR context. Ensure reads of multi-byte shared data are consistent (not torn).
37
What is a critical section and how do you implement it?
Answer
A critical section is code that must execute atomically without interruption. Implementation: save interrupt state, disable interrupts, execute critical code, restore interrupt state. In ARM: uint32_t primask = __get_PRIMASK(); __disable_irq(); /* critical code */ __set_PRIMASK(primask); This is nestable - inner calls don't accidentally re-enable interrupts.
38
Explain interrupt priority and preemption in ARM Cortex-M.
Answer
Cortex-M uses a programmable NVIC with configurable priorities (0 = highest). Lower priority numbers preempt higher numbers. Priorities are split into group (preemption) and sub (ordering) priorities via PRIGROUP. A running ISR is preempted only by higher-priority (lower-number) interrupts. BASEPRI register can mask interrupts below a threshold. PendSV and SysTick are typically lowest priority.
39
What is interrupt latency and how do you minimize it?
Answer
Interrupt latency is the time from interrupt assertion to first ISR instruction execution. It includes: finish current instruction, save context, fetch vector, pipeline flush. Cortex-M3/M4 achieves 12 cycles minimum. Minimize by: keeping ISRs short, avoiding long critical sections, using tail-chaining (back-to-back ISRs skip save/restore), and enabling late-arriving optimization.
40
What is the difference between a polling approach and interrupt-driven I/O?
Answer
Polling: CPU continuously checks a status flag in a loop - simple but wastes CPU cycles and has unpredictable response time. Interrupt-driven: CPU executes other code until hardware signals via interrupt - efficient CPU usage, deterministic response, but requires careful ISR design. Hybrid approach: interrupt sets a flag, main loop processes data, combining responsiveness with simplicity.
41
What is DMA and why is it important in embedded systems?
Answer
DMA (Direct Memory Access) transfers data between peripherals and memory without CPU intervention. The CPU configures the DMA controller (source, destination, count, direction) and the transfer happens in the background. This frees the CPU for computation during data transfers. Critical for: ADC sampling, SPI/UART bulk transfers, memory-to-memory copies, and audio/sensor streaming.
42
What is the difference between a counting semaphore and a binary semaphore?
Answer
A binary semaphore has only two states (0 or 1) - similar to a mutex but without ownership. A counting semaphore maintains a count (0 to N) - useful for managing a pool of resources or counting events. In RTOS context: binary semaphores are for signaling (ISR to task), counting semaphores for resource pools (e.g., 3 UART channels). Mutexes add priority inheritance.
43
What is priority inversion and how is it solved?
Answer
Priority inversion occurs when a high-priority task waits for a resource held by a low-priority task, which is preempted by a medium-priority task - effectively the high-priority task is blocked by the medium one. Solution: Priority Inheritance Protocol - the low-priority task temporarily inherits the high-priority task's priority while holding the shared resource. AUTOSAR OS uses this.
44
What is a watchdog timer and how is it used?
Answer
A watchdog timer is a hardware counter that resets the MCU if not periodically serviced ('kicked'). It detects software hangs, infinite loops, and deadlocks. Implementation: configure timeout period, periodically write to watchdog register in main loop. Window watchdog variants require servicing within a specific time window (not too early, not too late), detecting both stuck and runaway code.
45
Explain the concept of a timer interrupt for periodic task scheduling.
Answer
A hardware timer generates an interrupt at a fixed period (e.g., 1ms SysTick). The ISR increments a counter and sets flags for tasks at different rates: every tick for 1ms tasks, every 10th for 10ms tasks. Main loop checks flags and executes corresponding functions. This is a simple cooperative scheduler. RTOS uses timer interrupts for preemptive context switching.
46
What is the OSEK/VDX OS standard?
Answer
OSEK is the automotive real-time operating system standard, mandated by AUTOSAR Classic. Features: static configuration (all tasks, alarms, and resources defined at compile time), priority-based preemptive scheduling, conformance classes (BCC1, BCC2, ECC1, ECC2), counter/alarm mechanism for periodic activation, and resource management with priority ceiling protocol. It has minimal RAM/ROM footprint.
47
How do you measure execution time of a function in embedded C?
Answer
Methods: 1) Toggle GPIO pin, measure with oscilloscope - most accurate. 2) Read hardware timer before/after: uint32_t t0 = TIM2->CNT; func(); uint32_t dt = TIM2->CNT - t0; 3) Use Cortex-M DWT cycle counter: CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= 1; func(); cycles = DWT->CYCCNT; 4) Debugger trace (TRACE32 runtime measurement).
48
What is the difference between hard real-time and soft real-time?
Answer
Hard real-time: missing a deadline causes system failure (e.g., airbag deployment must fire within 15ms). Soft real-time: missing a deadline degrades performance but isn't catastrophic (e.g., infotainment display update). Automotive safety functions require hard real-time guarantees. This drives WCET (Worst-Case Execution Time) analysis, deterministic scheduling, and bounded interrupt latency.
49
How do you configure a GPIO pin in embedded C?
Answer
Typical sequence: 1) Enable clock for GPIO port: RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; 2) Set mode (input/output/alternate/analog): GPIOA->MODER = (GPIOA->MODER & ~(3<<(2*pin))) | (1<<(2*pin)); 3) Set output type (push-pull/open-drain): GPIOA->OTYPER; 4) Set speed: GPIOA->OSPEEDR; 5) Set pull-up/pull-down: GPIOA->PUPDR; Write: GPIOA->ODR or GPIOA->BSRR; Read: GPIOA->IDR.
50
Explain SPI communication protocol.
Answer
SPI (Serial Peripheral Interface) is a full-duplex synchronous protocol with 4 signals: SCLK (clock), MOSI (master out), MISO (master in), SS/CS (chip select, active low). Master generates clock and selects slave. Data is exchanged simultaneously on MOSI and MISO. Modes (CPOL/CPHA) define clock polarity and data sampling edge. Used for: flash memory, ADCs, display drivers, sensor ICs.
51
What is I2C and how does it differ from SPI?
Answer
I2C uses 2 wires (SDA data, SCL clock) with open-drain outputs and pull-up resistors. It supports multiple masters and slaves on one bus using 7-bit addressing. I2C is half-duplex, slower (100kHz-3.4MHz vs SPI's 50MHz+), but uses fewer pins. Start/stop conditions, ACK/NACK, and arbitration are built into the protocol. Used for: EEPROMs, temperature sensors, RTC, and low-speed peripherals.
52
How does UART communication work?
Answer
UART (Universal Asynchronous Receiver/Transmitter) is asynchronous serial with TX and RX lines. Frame: start bit (low), 8 data bits, optional parity, 1-2 stop bits (high). Both sides must agree on baud rate. No clock signal - timing is derived from baud rate. Implementation: configure baud rate register (USART_BRR), enable TX/RX, write to data register, use interrupts or DMA for reception.
53
What is the difference between polling, interrupt, and DMA-based UART reception?
Answer
Polling: CPU loops checking RXNE flag, reads data register - simple but blocks CPU. Interrupt: RXNE interrupt fires, ISR reads byte into buffer - efficient CPU usage but overhead per byte. DMA: DMA controller fills buffer automatically, interrupt on half/complete transfer - most efficient for high-throughput, frees CPU completely. For automotive ECUs, DMA with circular buffer is preferred for CAN/UART.
54
How do you implement a software debounce for a button input?
Answer
Sample the GPIO periodically (e.g., every 10ms in timer ISR). Maintain a counter: if state matches target, increment; if counter reaches threshold (e.g., 3 samples = 30ms), register state change and reset counter; if state changes, reset counter. Alternative: shift register approach - shift new sample into a uint8_t, trigger only when all bits match (0xFF = pressed, 0x00 = released).
55
What is PWM and how is it generated using a timer?
Answer
PWM (Pulse Width Modulation) generates a variable duty-cycle square wave. Timer counts up to a period value (ARR register), the output is high when counter < compare value (CCR register). Duty = CCR/ARR × 100%. Configuration: set timer prescaler for frequency, ARR for period, CCR for duty, enable PWM output mode. Used for: motor speed control, LED dimming, servo positioning, and DAC emulation.
56
How do you read an analog value using an ADC?
Answer
1) Enable ADC clock. 2) Configure channel and sampling time. 3) Set resolution (12-bit typical). 4) Start conversion: ADC1->CR2 |= ADC_CR2_SWSTART; 5) Wait for EOC flag: while(!(ADC1->SR & ADC_SR_EOC)); 6) Read result: uint16_t val = ADC1->DR; For continuous sampling, use DMA to transfer results to a buffer. Multiple channels use scan mode with DMA for simultaneous conversion.
57
What is a bootloader jump in embedded C?
Answer
To jump from bootloader to application: 1) Disable all interrupts. 2) Set the MSP (Main Stack Pointer) to the application's initial SP value: __set_MSP(*(uint32_t*)APP_ADDR); 3) Get the reset handler address: void (*app_reset)(void) = (void(*)(void))(*(uint32_t*)(APP_ADDR + 4)); 4) Jump: app_reset(); The application's vector table starts at APP_ADDR with SP followed by reset handler address.
58
How does CAN communication work at the driver level?
Answer
CAN controller has TX mailboxes and RX FIFOs. To transmit: write ID, DLC, and data to a free TX mailbox, set transmit request bit. The controller handles arbitration, bit stuffing, CRC, and ACK automatically. For reception: configure acceptance filters (ID/mask), enable RX FIFO interrupt, read message from FIFO when not empty. Bit timing registers set baud rate from prescaler and time segments.
59
What is the difference between Big-Endian and Little-Endian in CAN communication?
Answer
CAN signals use either byte order: Intel/Little-Endian (LSByte first) or Motorola/Big-Endian (MSByte first). The DBC file specifies each signal's byte order. When extracting a signal, you must correctly handle byte order: for a 16-bit Motorola signal spanning bytes 2-3: value = (data[2] << 8) | data[3]; for Intel: value = data[2] | (data[3] << 8). Incorrect byte order is a very common integration bug.
60
How do you implement a state machine in embedded C?
Answer
Use a function pointer table or switch-case. Function pointer approach: typedef void (*StateFunc)(void); StateFunc stateTable[] = {stateIdle, stateRunning, stateError}; currentState = STATE_IDLE; while(1) { stateTable[currentState](); } Each state function handles events and sets currentState for transitions. This is cleaner than nested switch-cases and scales better. Add entry/exit actions for Moore-style machines.
61
What is the __attribute__((packed)) directive?
Answer
packed tells the compiler to remove all padding between struct members, placing them at consecutive byte addresses regardless of natural alignment. struct __attribute__((packed)) { uint8_t a; uint32_t b; } is 5 bytes instead of 8. Used for: parsing communication frames, matching hardware register layouts, and binary file formats. Caveat: accessing packed unaligned members may cause faults on some architectures or performance penalties.
62
What is the static keyword used for in C?
Answer
Three uses: 1) Static local variable: retains value between function calls, stored in .data/.bss instead of stack. 2) Static global variable/function: limits scope to the current translation unit (file), providing encapsulation. 3) Static in function parameter (C99): static in array parameter declaration hints minimum size. In embedded, static locals are used for persistent state in ISR-callable functions.
63
What is the extern keyword and when is it used?
Answer
extern declares that a variable or function is defined in another translation unit. extern uint32_t systemTick; tells the compiler the variable exists elsewhere and the linker will resolve it. Without extern, defining a global in a header causes multiple definition errors. Best practice: declare extern in header, define in one .c file. extern "C" in C++ prevents name mangling for C linkage.
64
What is the difference between #define and const in C?
Answer
#define is a preprocessor text substitution - no type checking, no scope, no address, evaluated at compile time. const creates a typed, scoped variable with an address - benefits from type checking and debugging visibility. MISRA-C prefers const and enum over #define for type safety. However, #define is needed for compile-time constants in array sizes (pre-C99) and conditional compilation.
65
What is the sizeof operator and what are its pitfalls?
Answer
sizeof returns the size in bytes of a type or expression at compile time. Pitfalls: sizeof(array) gives total bytes only in the defining scope - through a pointer it gives pointer size (4 or 8 bytes), not array size. sizeof(struct) includes padding. sizeof doesn't evaluate its operand - sizeof(x++) doesn't increment x. For strings, sizeof includes the null terminator but strlen doesn't.
66
How do you optimize code for ROM/RAM on resource-constrained MCUs?
Answer
ROM savings: use const for lookup tables (stays in flash), reduce dead code, use -Os optimization, avoid printf/sprintf (use custom formatters), use function pointers instead of large switch. RAM savings: minimize global variables, use smaller data types, share buffers, reduce stack sizes. Both: review linker map file, use compiler sections for placement, consider code compression on targets that support it.
67
What is tail call optimization?
Answer
When a function's last action is calling another function, the compiler can replace the call with a jump, reusing the current stack frame. This prevents stack growth in recursive patterns. Example: return other_func(x); becomes a jump. GCC enables this at -O2+. In embedded C, this is important for state machines implemented as recursive function calls, though MISRA prohibits recursion.
68
Explain the inline keyword in C.
Answer
inline suggests the compiler replace function calls with the function body, eliminating call overhead. In C99, inline in a header allows the definition in multiple translation units. The compiler may ignore the hint. For embedded: inline is useful for small, frequently called functions (bit manipulation, register access). Too much inlining increases code size. MISRA-C permits inline functions.
69
What is the restrict keyword in C99?
Answer
restrict tells the compiler that a pointer is the only way to access the memory it points to - no aliasing. This enables optimizations: void copy(int *restrict dst, const int *restrict src, int n). The compiler can use registers and reorder operations knowing dst and src don't overlap. Violating the promise causes undefined behavior. Useful for DMA buffer operations and DSP routines.
70
How do you write position-independent code (PIC) for embedded?
Answer
PIC uses relative addressing so code can execute at any address without modification. Techniques: use PC-relative branches (default on ARM), access globals through a Global Offset Table (GOT), avoid absolute addresses in code. PIC is needed for bootloaders that relocate code, firmware patches loaded at variable addresses, and shared libraries. GCC: -fpic or -fPIC flags.
71
What is the role of the startup file (crt0) in embedded C?
Answer
The startup file runs before main(): 1) Sets the initial stack pointer. 2) Copies .data section from flash to RAM (initialized globals). 3) Zeroes .bss section (uninitialized globals). 4) Initializes the FPU if present. 5) Calls SystemInit() for clock configuration. 6) Calls __libc_init_array() for C++ constructors. 7) Calls main(). It also contains the vector table with ISR addresses.
72
How do you handle floating-point arithmetic in embedded C?
Answer
Options: 1) Hardware FPU (Cortex-M4F/M7): enable via compiler flags (-mfpu=fpv4-sp-d16 -mfloat-abi=hard), fastest. 2) Software FPU: compiler generates library calls, slow but portable. 3) Fixed-point arithmetic: use scaled integers (e.g., Q15 format: value = integer / 32768), most deterministic. For AUTOSAR, fixed-point is common for MISRA compliance and deterministic execution time.
73
What tools and techniques are used for embedded C code review?
Answer
Static analysis: Polyspace, LDRA, QAC for MISRA compliance and bug detection. Dynamic analysis: Valgrind (on host), AddressSanitizer for memory issues. Code review tools: Gerrit, Crucible for peer review. Metrics: cyclomatic complexity, function length, nesting depth. Unit testing: VectorCAST, Tessy, GoogleTest with hardware abstraction. Runtime checks: stack watermarking, assertion macros, defensive programming.