C course menu
Advanced C Interview Questions: UB, volatile, Memory Layout, Endianness and Embedded C
Advanced C interview questions with answers: undefined, unspecified and implementation-defined behavior, volatile, the memory layout of a C program, stack vs heap, structure padding, endianness, restrict, inline, setjmp and longjmp, bit fields, and interview questions for embedded systems roles.
On this page
- 1. What is undefined behavior, and how does it differ from unspecified and implementation-defined behavior?
- 2. What does volatile do? When do you need it?
- 3. Describe the memory layout of a running C program.
- 4. Stack versus heap?
- 5. What is structure padding, and how can you reduce it?
- 6. What is endianness? Write code to detect it.
- 7. How do you swap the byte order of a 32-bit value?
- 8. What are bit fields?
- 9. What do inline and restrict do?
- 10. What are setjmp and longjmp?
- 11. Why is gets dangerous, and what replaced it?
- 12. Embedded: how do you set, clear and test bit 5 of a register?
- 13. What does this declaration mean: char ((*x)(void))[5]?
- Keep going
These questions appear in interviews for systems, embedded and experienced developer roles. They test whether you understand what the compiler and the machine actually do with your code.
1. What is undefined behavior, and how does it differ from unspecified and implementation-defined behavior?#
Show solution
- Undefined: the standard sets no requirements. Examples: signed overflow, out-of-bounds access, null dereference,
i = i++. - Unspecified: one of several outcomes, and the compiler need not document which. Example: the order in which function arguments are evaluated.
- Implementation-defined: the compiler must choose and document. Examples: the size of
int, right-shifting negative numbers.
Optimisers assume UB never happens, so UB can remove checks or change behavior between -O0 and -O2. See Undefined Behavior.
2. What does volatile do? When do you need it?#
Show solution
volatile tells the compiler that a variable can change in ways it cannot see, so every read and write must really happen in the order written. Without it, the compiler may keep the value in a register and skip "redundant" reads. Use it for:
- memory-mapped hardware registers;
- variables shared with an interrupt service routine;
- a
volatile sig_atomic_tflag set in a signal handler.
#define STATUS_REG (*(volatile uint32_t *)0x40021000u) /* example address */
while ((STATUS_REG & 0x1u) == 0) {
/* wait for the hardware ready bit */
}volatile does not make code thread-safe: it gives no atomicity and no ordering between threads. Use C11 <stdatomic.h> or locks for that.
3. Describe the memory layout of a running C program.#
Show solution
From low to high addresses, typically:
The exact layout depends on the operating system and toolchain.
4. Stack versus heap?#
Show solution
5. What is structure padding, and how can you reduce it?#
Show solution
Compilers align members to addresses that are multiples of their size, inserting unused bytes. Ordering members from largest to smallest usually reduces padding:
#include <stdio.h>
struct Bad { char a; double b; char c; };
struct Good { double b; char a; char c; };
int main(void) {
printf("%zu %zu\n", sizeof(struct Bad), sizeof(struct Good));
return 0;
}24 16(Sizes from x86-64 GCC.) Compiler extensions such as __attribute__((packed)) remove padding, but unaligned access can be slower or even fault on some processors.
6. What is endianness? Write code to detect it.#
Show solution
Endianness is the order in which a multi-byte value's bytes are stored. Little-endian (x86, most ARM systems) stores the least significant byte first; big-endian stores the most significant byte first, and is also the standard "network byte order":
#include <stdio.h>
#include <stdint.h>
int main(void) {
uint32_t x = 0x01020304;
unsigned char *bytes = (unsigned char *)&x;
printf("%02X %02X %02X %02X\n", bytes[0], bytes[1], bytes[2], bytes[3]);
printf("%s-endian\n", bytes[0] == 0x04 ? "little" : "big");
return 0;
}04 03 02 01
little-endianThis output is from an x86-64 machine. Inspecting an object's bytes through an unsigned char * is always allowed in C.
7. How do you swap the byte order of a 32-bit value?#
Show solution
Shift each byte to its new position and combine with OR:
#include <stdio.h>
#include <stdint.h>
uint32_t swap32(uint32_t x) {
return (x >> 24) |
((x >> 8) & 0x0000FF00u) |
((x << 8) & 0x00FF0000u) |
(x << 24);
}
int main(void) {
printf("0x%08X\n", (unsigned)swap32(0x12345678u));
return 0;
}0x78563412On POSIX systems, htonl and ntohl convert between host and network byte order.
8. What are bit fields?#
Show solution
Struct members with an explicit width in bits, which pack several small values into one integer:
#include <stdio.h>
struct Flags {
unsigned ready : 1;
unsigned error : 1;
unsigned mode : 3; /* 0..7 */
};
int main(void) {
struct Flags f = { .ready = 1, .mode = 5 };
printf("%u %u %u %zu\n", f.ready, f.error, f.mode, sizeof f);
return 0;
}1 0 5 4The order and layout of bit fields are implementation-defined, so for hardware registers and file formats, explicit masks and shifts are more portable.
9. What do inline and restrict do?#
Show solution
inlinesuggests that calls be replaced by the function body to avoid call overhead. In C it is only a hint, andstatic inlinefunctions in headers are the usual safe pattern.restricton a pointer promises the object is accessed only through that pointer, which allows better optimisation.memcpy's parameters arerestrict, which is why overlapping copies needmemmove.
10. What are setjmp and longjmp?#
Show solution
setjmp saves the current execution context; a later longjmp jumps back to it, unwinding the stack without returning normally. Some C libraries use them for error recovery, similar to exceptions:
#include <setjmp.h>
#include <stdio.h>
static jmp_buf on_error;
void parse(int value) {
if (value < 0) longjmp(on_error, 1);
printf("parsed %d\n", value);
}
int main(void) {
if (setjmp(on_error) == 0) {
parse(5);
parse(-1);
printf("not reached\n");
} else {
printf("recovered from error\n");
}
return 0;
}parsed 5
recovered from errorResources acquired between the two calls are not released automatically, and local variables changed after setjmp must be volatile to have reliable values. Use them sparingly.
11. Why is gets dangerous, and what replaced it?#
Show solution
gets reads a line into a buffer without knowing its size, so long input overflows the buffer, a classic security hole. It was removed in C11. Use fgets(buf, sizeof buf, stdin) and strip the trailing newline.
12. Embedded: how do you set, clear and test bit 5 of a register?#
Show solution
#include <stdio.h>
#include <stdint.h>
int main(void) {
volatile uint8_t reg = 0x81; /* stands in for a hardware register */
reg |= (uint8_t)(1u << 5); /* set */
printf("0x%02X %u\n", reg, (reg >> 5) & 1u);
reg &= (uint8_t)~(1u << 5); /* clear */
printf("0x%02X %u\n", reg, (reg >> 5) & 1u);
return 0;
}0xA1 1
0x81 0On real hardware the register is reached through a volatile pointer to its address, and a read-modify-write like |= may need interrupts disabled if an interrupt handler also writes that register.
13. What does this declaration mean: char ((*x)(void))[5]?#
Show solution
Read from the name outwards: x is a pointer to a function taking no arguments, returning a pointer to an array of 5 elements, each a pointer to char. In real code, build such types from typedefs:
typedef char *Row[5]; /* array of 5 char pointers */
typedef Row *(*Getter)(void); /* pointer to function returning Row * */
Getter x;Keep going#
- C coding challenges: implement classic algorithms in C.
- Undefined Behavior and Bitwise Operators for the background.
Finished this lesson?