C Pointers Interview Questions and Answers (40 Questions with Code Examples)

Pointers are the single most reliable way for an interviewer to separate someone who has memorised C syntax from someone who understands how a program sits in memory. This guide collects 40 C pointers interview questions for embedded and firmware roles. Each answer is short, shows code you can compile, and calls out the mistake interviewers usually probe for. Examples assume a standard C compiler such as GCC or Clang; where behaviour depends on the target (word size, alignment, endianness) the answer says so.

Topics Covered

  1. Pointer Basics (Q1–Q8)
  2. Arrays and Pointer Arithmetic (Q9–Q15)
  3. Pointers and Functions (Q16–Q21)
  4. const, volatile and restrict (Q22–Q26)
  5. Dynamic Memory (Q27–Q32)
  6. Pointers in Embedded Code (Q33–Q38)
  7. Tricky Cases and Undefined Behaviour (Q39–Q40)
Pointer Basics
Q1What is a pointer in C and what does it actually store?

A pointer is a variable whose value is the address of another object or function. The pointer's type tells the compiler how many bytes to read when you dereference it and how far to move when you do arithmetic on it.

int x = 10;
int *p = &x;      /* p holds the address of x */
printf("%d\n", *p);  /* prints 10 */
*p = 20;             /* x is now 20 */
💡 Say "address plus type information", not just "address". The type is what makes p + 1 move by sizeof(*p) bytes.
Q2What is the size of a pointer?

It is implementation-defined. It is typically 4 bytes on 32-bit targets such as Cortex-M and 8 bytes on 64-bit targets such as AArch64 or x86-64. All object pointers on a given platform usually have the same size, but the C standard does not guarantee that a function pointer is the same size as a data pointer. Always use sizeof instead of assuming.

printf("%zu %zu\n", sizeof(char *), sizeof(double *));  /* equal on common platforms */
Q3What is a NULL pointer, and what happens if you dereference it?

A null pointer is a pointer guaranteed not to point to any valid object. Dereferencing it is undefined behaviour. On a hosted OS you usually get a segmentation fault. On a bare-metal MCU there may be no MMU, and address 0 can be valid memory (for example the vector table), so the bug can silently read or corrupt data instead of crashing.

int *p = NULL;
if (p != NULL) {
    *p = 1;     /* guarded */
}
💡 The null pointer constant is 0 or NULL in source code, but the machine representation does not have to be all-zero bits. Comparing against NULL is always correct.
Q4What is a wild pointer? How is it different from a dangling pointer?

A wild pointer is declared but never initialised, so it holds an indeterminate value. A dangling pointer was valid once but the object it pointed to no longer exists, for example after free() or when a function returns.

int *wild;                 /* uninitialised: wild */
int *d = malloc(sizeof *d);
free(d);                   /* d is now dangling */
d = NULL;                  /* defensive habit */
Q5What is a void pointer and when is it used?

void * is a generic object pointer. Any object pointer converts to it and back without a cast in C. You cannot dereference it or (in standard C) do arithmetic on it, because the size of the target is unknown. GCC allows arithmetic on void * as an extension, but it is not portable.

void swap_bytes(void *a, void *b, size_t n)
{
    unsigned char *x = a, *y = b, t;
    while (n--) { t = *x; *x++ = *y; *y++ = t; }
}
💡 Typical uses: memcpy, qsort comparators, and RTOS APIs that pass a context pointer to a task or callback.
Q6What is the difference between *p++, (*p)++ and *++p?
ExpressionMeaning
*p++Read *p, then advance the pointer (postfix binds tighter than dereference)
(*p)++Increment the pointed-to value; pointer unchanged
*++pAdvance the pointer first, then read
int a[] = {10, 20, 30};
int *p = a;
int v = *p++;   /* v = 10, p -> a[1] */
Q7What is a pointer to a pointer, and why would you use it?

A T ** stores the address of a T *. The classic reason is letting a function change the caller's pointer, because C passes arguments by value.

int alloc_buf(uint8_t **out, size_t n)
{
    *out = malloc(n);
    return (*out != NULL) ? 0 : -1;
}

uint8_t *buf;
if (alloc_buf(&buf, 64) == 0) { /* use buf */ free(buf); }
Q8Can you compare two pointers? When is it valid?

Equality (==, !=) works for any two pointers of compatible type. Relational comparison (<, >) is only defined when both pointers point into the same array (or one past its end) or the same struct. Comparing unrelated pointers is undefined by the standard, even though it "works" on flat-memory targets.

Arrays and Pointer Arithmetic
Q9What is the relationship between arrays and pointers?

An array is not a pointer, but in most expressions an array name decays to a pointer to its first element. a[i] is defined as *(a + i). The exceptions are sizeof, unary &, and string-literal initialisation of a char array.

int a[4] = {1, 2, 3, 4};
printf("%zu\n", sizeof a);      /* 16 with 4-byte int */
int *p = a;
printf("%zu\n", sizeof p);      /* pointer size, not 16 */
Q10Why does sizeof on an array parameter return the pointer size?

A parameter declared as int a[10] is adjusted by the compiler to int *a. The length is lost, so pass it explicitly.

void f(int a[10]) { printf("%zu\n", sizeof a); }  /* prints sizeof(int *) */
void g(const int *a, size_t n);                    /* correct pattern */
Q11How does pointer arithmetic work?

p + n advances by n * sizeof(*p) bytes. Subtracting two pointers into the same array yields a ptrdiff_t counted in elements, not bytes.

uint32_t buf[8];
uint32_t *a = &buf[1], *b = &buf[5];
ptrdiff_t n = b - a;                         /* 4 elements */
size_t bytes = (char *)b - (char *)a;        /* 16 bytes */
Q12What is the difference between int *p[5] and int (*p)[5]?

Read declarations from the identifier outward. int *p[5] is an array of 5 pointers to int. int (*p)[5] is a pointer to an array of 5 ints.

int m[3][5];
int (*row)[5] = m;     /* row[i][j] works; row + 1 skips 5 ints */
💡 Pointer-to-array is how you correctly pass a 2-D array: void f(int (*m)[5], size_t rows).
Q13What is the difference between char *s = "abc" and char s[] = "abc"?

char s[] = "abc" creates a modifiable 4-byte array initialised from the literal. char *s = "abc" points at a string literal that typically lives in read-only memory; modifying it is undefined behaviour. On MCUs the literal is usually in flash, so a write may fault or be silently ignored.

char a[] = "abc";  a[0] = 'X';   /* OK */
char *p  = "abc";  /* p[0] = 'X'; undefined behaviour */
const char *q = "abc";           /* preferred spelling */
Q14Is a[i] the same as i[a]?

Yes, because a[i] means *(a + i) and addition is commutative. It compiles, but never write it in real code.

Q15How do you find the length of a string with pointers only?
size_t my_strlen(const char *s)
{
    const char *p = s;
    while (*p) p++;
    return (size_t)(p - s);
}

Interviewers often follow up with "what if s is NULL?" and "what if the buffer is not terminated?". The standard strlen does not protect against either; bounded variants such as strnlen help in the second case.

Pointers and Functions
Q16What is a function pointer? Show the syntax.

A function pointer stores the address of a function so it can be called indirectly.

int add(int a, int b) { return a + b; }

int (*op)(int, int) = add;     /* or &add */
int r = op(2, 3);              /* 5 */

typedef int (*binop_t)(int, int);
binop_t table[] = { add };
Q17Where are function pointers used in embedded software?

Interrupt vector tables, driver ops tables (open, read, write style structs), state machines, callback registration, and bootloaders jumping to an application entry point.

typedef void (*isr_t)(void);
static isr_t vector[16];

void register_isr(unsigned n, isr_t fn) { if (n < 16) vector[n] = fn; }
Q18Why can't a function swap two ints if you pass them by value, and how do pointers fix it?

The callee receives copies. Passing addresses lets it modify the originals.

void swap(int *a, int *b)
{
    int t = *a; *a = *b; *b = t;
}
Q19What is wrong with returning a pointer to a local variable?

The local lives on the stack frame, which is gone after return, so the pointer dangles. Fixes: return a static object (not thread-safe), allocate with malloc, or have the caller pass a buffer.

char *bad(void)  { char b[16] = "hi"; return b; }          /* wrong */
int   good(char *out, size_t n) { return snprintf(out, n, "hi"); }
Q20How do you call a function at a fixed address?

Cast the integer address to a function pointer type. A bootloader on a Cortex-M typically reads the application's reset vector from its vector table and jumps to it after loading the stack pointer.

void (*app)(void) = (void (*)(void))0x08008000u;  /* illustrative address */
app();
💡 On Thumb targets the address must have bit 0 set to stay in Thumb state; real bootloaders take the value straight from the vector table, which already has it set.
Q21What is a callback and how do you pass user context to it?

A callback is a function pointer handed to another module to be called later. Because C has no closures, APIs take a void *ctx alongside the function pointer.

typedef void (*done_cb)(int status, void *ctx);
int  start_transfer(const uint8_t *d, size_t n, done_cb cb, void *ctx);
const, volatile and restrict
Q22Explain const int *p, int *const p and const int *const p.
DeclarationCan change *p?Can change p?
const int *pNoYes
int *const pYesNo
const int *const pNoNo
💡 Read right to left: "p is a pointer to const int".
Q23What does volatile mean on a pointer target?

volatile tells the compiler that each access to the object is observable and must not be removed, merged or reordered with other volatile accesses. It is needed for memory-mapped registers and for data changed by an ISR or hardware. For more on why this matters in kernel code, see our article on why volatile is used in the Linux kernel and device drivers.

volatile uint32_t *const UART_SR = (volatile uint32_t *)0x40011000u;
while ((*UART_SR & 0x80u) == 0) { /* wait for TXE */ }
Q24Can a variable be both const and volatile?

Yes. Your code may not write to it, but hardware can change it. A read-only status register is the standard example: const volatile uint32_t *STATUS.

Q25What does restrict promise?

C99 restrict is a promise from the programmer that, during the pointer's lifetime, the object it accesses is accessed only through that pointer (or pointers derived from it). It lets the compiler optimise more aggressively. Breaking the promise is undefined behaviour, which is why memcpy (restrict) is unsafe for overlapping buffers while memmove is safe.

void add_arrays(int *restrict dst, const int *restrict a, const int *restrict b, size_t n);
Q26Is it valid to cast away const?

The cast itself is legal, but writing through the result is undefined behaviour if the original object was defined const. It is acceptable only when the object was never really const, for example to adapt to a legacy API that forgot a const in its signature and does not modify the data.

Dynamic Memory
Q27How do malloc, calloc and realloc differ?
FunctionBehaviour
malloc(n)Allocates n bytes, contents uninitialised
calloc(c, n)Allocates c*n bytes, zero-filled, with overflow checking on the multiplication
realloc(p, n)Resizes a block; may move it; returns NULL on failure and leaves the old block valid
Q28What is the right way to use realloc?
void *tmp = realloc(buf, new_size);
if (tmp == NULL) {
    /* buf is still valid: handle error, free(buf) if giving up */
} else {
    buf = tmp;
}

Writing buf = realloc(buf, n) leaks the original block when allocation fails.

Q29What happens if you call free twice on the same pointer?

Undefined behaviour; typically heap corruption or an abort from the allocator. free(NULL) is explicitly safe, so setting the pointer to NULL after freeing turns an accidental second free into a no-op.

Q30Why do many embedded coding standards forbid malloc?

Fragmentation, non-deterministic allocation time, and unchecked failure paths are all hard to accept on long-running or safety-related systems. Common alternatives are static allocation, fixed-block memory pools, and allocating everything during initialisation. For the memory-region basics see our related guide Top 40 Embedded Systems Interview Questions.

Q31How do you detect a memory leak?

On a host, use Valgrind (--leak-check=full) or AddressSanitizer/LeakSanitizer (-fsanitize=address). On a target, wrap the allocator to count live blocks, track high-water marks, and use the RTOS heap statistics (FreeRTOS exposes xPortGetFreeHeapSize() and xPortGetMinimumEverFreeHeapSize() with its heap implementations).

Q32Why should sizeof be used with the variable instead of the type in malloc?
struct dev *d = malloc(sizeof *d);   /* stays correct if the type changes */

No cast is required in C, and casting hides a missing #include <stdlib.h> on very old compilers.

Pointers in Embedded Code
Q33How do you access a memory-mapped register with a pointer?
#define GPIOA_ODR  (*(volatile uint32_t *)0x40020014u)
GPIOA_ODR |= (1u << 5);

A structure overlay is cleaner for a block of registers:

typedef struct {
    volatile uint32_t CR;
    volatile uint32_t SR;
    volatile uint32_t DR;
} uart_regs_t;

#define UART1 ((uart_regs_t *)0x40011000u)
UART1->CR |= 1u;
💡 Addresses above are illustrative. Always take the real ones from the device reference manual. For the Linux equivalent (ioremap, readl/writel) see our post on memory-mapped I/O vs port-mapped I/O.
Q34What problems does structure overlay have?

Padding and alignment must match the register layout (use fixed-width types and, if needed, explicit reserved fields and static asserts on offsets), bit-field ordering is implementation-defined, and each access must keep the correct width. Prefer shifts and masks over bit-fields for registers.

Q35What is an unaligned access and why can it fault?

Reading a 32-bit value from an address that is not a multiple of 4. Cortex-M0/M0+ will raise a HardFault; Cortex-M3 and above permit it for most loads but not all instructions, and many other architectures either fault or are slow. Casting a uint8_t * into a packet buffer to uint32_t * is a common source of this bug.

uint32_t read_u32(const uint8_t *p)
{
    uint32_t v;
    memcpy(&v, p, sizeof v);   /* safe and usually compiled to one load */
    return v;
}
Q36How can you check endianness using pointers?
uint16_t x = 0x0001;
int little = (*(uint8_t *)&x == 1);

Accessing any object through a char-type pointer is always allowed, which is why this works. Casting to a different non-character type would violate strict aliasing.

Q37What is strict aliasing?

The compiler may assume that pointers of unrelated types do not refer to the same memory. Code such as *(uint32_t *)&some_float breaks that assumption, and optimised builds may produce surprising results. Use memcpy, a union (permitted in C), or compile with -fno-strict-aliasing as a last resort.

Q38How do you pass a ring buffer between an ISR and the main loop safely?

Use a single-producer, single-consumer ring buffer where the ISR only writes the head and the main loop only writes the tail, mark the indices volatile, and make sure index updates are atomic for the word size. If more than one writer exists, disable interrupts around the update or use atomics.

typedef struct {
    uint8_t buf[64];
    volatile uint8_t head, tail;     /* power-of-two size: mask instead of modulo */
} ring_t;

static inline int ring_put(ring_t *r, uint8_t b)
{
    uint8_t next = (r->head + 1) & 63;
    if (next == r->tail) return -1;  /* full */
    r->buf[r->head] = b;
    r->head = next;
    return 0;
}
💡 On cores with caches or out-of-order behaviour you may also need memory barriers. Mention that you know the limit of volatile: it is not a synchronisation primitive.
Tricky Cases and Undefined Behaviour
Q39Reverse a singly linked list in place.
struct node { int v; struct node *next; };

struct node *reverse(struct node *head)
{
    struct node *prev = NULL;
    while (head) {
        struct node *nx = head->next;
        head->next = prev;
        prev = head;
        head = nx;
    }
    return prev;
}

Walk through a three-node list on paper. Interviewers care that you save next before overwriting it and that you handle the empty list.

Q40What is the output? Explain.
int a[] = {1, 2, 3, 4};
int *p = a;
printf("%d %d %d\n", *p++, *p++, *p);   /* unspecified */

The order in which function arguments are evaluated is unspecified, and modifying p twice without a sequence point between the modifications is not well defined in this form. The correct answer is "do not write this". A safe version splits it into separate statements.

int x = *p++;
int y = *p++;
int z = *p;
printf("%d %d %d\n", x, y, z);   /* 1 2 3 */

Practise Pointers the Way Interviewers Test Them

The Embedded System Interview Prep Bootcamp covers C, data structures, RTOS, Linux kernel and drivers, with a mock interview and resume review.

View Interview Prep Bootcamp →

Frequently Asked Questions

Frequently Asked Questions

Q: Why are pointers so important in embedded interviews?
Embedded code talks to hardware through addresses, passes buffers between interrupts and tasks, and runs without an OS safety net. Weak pointer knowledge shows up as bugs that are hard to debug on a target.

Q: How many pointer questions should I prepare?
Quality matters more than count. Be able to explain, with code, the topics in Q1, Q9, Q12, Q16, Q22, Q23, Q28, Q33, Q35 and Q39 without hesitation, then widen out.

Q: Is pointer arithmetic on void * allowed?
Not in standard C. GCC accepts it as an extension that treats the size as 1. Cast to char * or uint8_t * instead.

Q: Do I need to know C++ smart pointers for embedded C roles?
Not for C roles. For C++ roles it helps to know unique_ptr and how it avoids the leaks discussed in Q28 and Q29.

Q: Where should I practise next?
Compile the snippets here with -Wall -Wextra -fsanitize=address,undefined, then continue with our 50 must-know Embedded C interview questions.