Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Pointers & Memory
C

Pointers & Memory

Practice 21 important C questions covering pointers, memory management, pointer arithmetic, arrays, and related concepts.

1. What happens when calling free(ptr) on a dynamically allocated memory pointer in C?

Answer: The memory block is returned to the heap allocator and marked reusable, but ptr itself is unchanged — it keeps the address and becomes a dangling pointer.

free(ptr) tells the allocator that the memory at ptr may be reused. Two things it does not do:

  • It does not zero the memory or set ptr to NULL.
  • It does not retroactively invalidate other pointers that happen to point at the same address.

So after free, ptr still holds the old address, but the memory it points to is no longer yours. Using ptr afterward (reading or writing) is undefined behavior — that’s the classic dangling pointer. (Also: freeing the same block twice is a double-free, another UB.)

Good practice: set ptr = NULL immediately after free so the pointer is obviously invalid. The interview answer: free returns the block to the allocator but leaves ptr holding the stale address — a dangling pointer; dereferencing it is UB.

Answer:

The memory block is returned to the heap allocator and marked reusable, but ptr itself is unchanged — it keeps the address and becomes a dangling pointer.

free(ptr) tells the allocator that the memory at ptr may be reused. Two things it does not do:

  • It does not zero the memory or set ptr to NULL.
  • It does not retroactively invalidate other pointers that happen to point at the same address.

So after free, ptr still holds the old address, but the memory it points to is no longer yours. Using ptr afterward (reading or writing) is undefined behavior — that’s the classic dangling pointer. (Also: freeing the same block twice is a double-free, another UB.)

Good practice: set ptr = NULL immediately after free so the pointer is obviously invalid. The interview answer: free returns the block to the allocator but leaves ptr holding the stale address — a dangling pointer; dereferencing it is UB.

2. What is the fundamental difference between malloc() and calloc()?

Answer: malloc(size) takes a single size and leaves memory uninitialized; calloc(num, size) takes count and element-size and zero-initializes every byte.

  • malloc(n) — allocates n bytes, uninitialized. The contents are garbage (whatever was in memory). Also does not check for overflow if you multiply sizes yourself.
  • calloc(n, size) — allocates n × size bytes and sets every byte to 0. It also has built-in overflow protection on the multiplication (returns NULL on overflow).
int *a = malloc(10 * sizeof(int));   // 10 ints, junk values
int *b = calloc(10, sizeof(int));    // 10 ints, all zeros

Both allocate from the heap and return void*. The zeroing costs a little time but can prevent uninitialized-memory bugs and is required for things like arrays you’ll partially fill. The interview answer: malloc takes one size and leaves memory uninitialized; calloc takes count and element size and zeroes all bytes (with overflow-safe multiplication).

Answer:

malloc(size) takes a single size and leaves memory uninitialized; calloc(num, size) takes count and element-size and zero-initializes every byte.

  • malloc(n) — allocates n bytes, uninitialized. The contents are garbage (whatever was in memory). Also does not check for overflow if you multiply sizes yourself.
  • calloc(n, size) — allocates n × size bytes and sets every byte to 0. It also has built-in overflow protection on the multiplication (returns NULL on overflow).
int *a = malloc(10 * sizeof(int));   // 10 ints, junk values
int *b = calloc(10, sizeof(int));    // 10 ints, all zeros

Both allocate from the heap and return void*. The zeroing costs a little time but can prevent uninitialized-memory bugs and is required for things like arrays you’ll partially fill. The interview answer: malloc takes one size and leaves memory uninitialized; calloc takes count and element size and zeroes all bytes (with overflow-safe multiplication).

3. What is the result of evaluating sizeof(arr) versus sizeof(ptr) where int arr[10]; int *ptr = arr; on a 64-bit architecture?

Answer: sizeof(arr) is 40 bytes (the whole array); sizeof(ptr) is 8 bytes (a pointer).

The key fact: arr keeps its array type in the scope where it’s declared. So sizeof(arr) gives the total byte size of the array: 10 × sizeof(int) = 10 × 4 = 40 bytes.

ptr is a plain pointer variable holding an address. Its size is the platform’s pointer size: 8 bytes on 64-bit.

The trap: if arr is passed to a function, it decays to a pointer, and sizeof inside the function gives 8, not 40. But in the declaring scope, sizeof(arr) is the array’s full size. The interview answer: sizeof(arr) = 40 (entire array), sizeof(ptr) = 8 (pointer on 64-bit).

Answer:

sizeof(arr) is 40 bytes (the whole array); sizeof(ptr) is 8 bytes (a pointer).

The key fact: arr keeps its array type in the scope where it’s declared. So sizeof(arr) gives the total byte size of the array: 10 × sizeof(int) = 10 × 4 = 40 bytes.

ptr is a plain pointer variable holding an address. Its size is the platform’s pointer size: 8 bytes on 64-bit.

The trap: if arr is passed to a function, it decays to a pointer, and sizeof inside the function gives 8, not 40. But in the declaring scope, sizeof(arr) is the array’s full size. The interview answer: sizeof(arr) = 40 (entire array), sizeof(ptr) = 8 (pointer on 64-bit).

4. What happens when an array parameter is passed to a function, as in void foo(int arr[10])?

Answer: The parameter decays to a pointer — it becomes int *arr, and the size information is lost inside foo.

Array parameters in function signatures are adjusted by the compiler to pointers. void foo(int arr[10]) is exactly equivalent to void foo(int *arr). Consequences:

  • sizeof(arr) inside foo returns the pointer size (8 on 64-bit), not 40.
  • arr is an address; the function has no idea how many elements exist.
  • You must pass the length separately (or use a sentinel) to know the array’s extent.
void foo(int arr[], int n) {   // arr[] also decays to int*
    for (int i = 0; i < n; ++i) { /* ... */ }
}

This is the fundamental reason C functions that take arrays always take a size parameter too. The interview answer: array parameters decay to pointers (int arr[10] becomes int *arr), so size info is lost inside the function.

Answer:

The parameter decays to a pointer — it becomes int *arr, and the size information is lost inside foo.

Array parameters in function signatures are adjusted by the compiler to pointers. void foo(int arr[10]) is exactly equivalent to void foo(int *arr). Consequences:

  • sizeof(arr) inside foo returns the pointer size (8 on 64-bit), not 40.
  • arr is an address; the function has no idea how many elements exist.
  • You must pass the length separately (or use a sentinel) to know the array’s extent.
void foo(int arr[], int n) {   // arr[] also decays to int*
    for (int i = 0; i < n; ++i) { /* ... */ }
}

This is the fundamental reason C functions that take arrays always take a size parameter too. The interview answer: array parameters decay to pointers (int arr[10] becomes int *arr), so size info is lost inside the function.

5. What does realloc(ptr, 0) do according to C standard implementations when ptr is a valid non-null pointer?

Answer: It’s implementation-defined — either frees the memory (returning NULL) or returns a non-dereferenceable pointer that must still be freed.

Passing 0 as the new size to realloc is a murky corner of the standard. Implementations may:

  • Free ptr and return NULL (common, feels like free).
  • Return a unique pointer to zero-size memory that you must still call free on (some platforms, e.g. historical glibc behavior).

Because the two behaviors differ (one lets you free the result, the other means you already lost the block and free(NULL) is fine — but if you ignore the returned pointer you could leak or double-free), the standard calls it implementation-defined. The C committee’s guidance in C11/C17: don’t use realloc(ptr, 0) — it’s ambiguous and its semantics were a known defect. Use free(ptr) to release, and realloc only with a positive size.

The interview answer: implementation-defined — either frees and returns NULL, or returns a non-dereferenceable pointer that still needs free; it should be avoided.

Answer:

It’s implementation-defined — either frees the memory (returning NULL) or returns a non-dereferenceable pointer that must still be freed.

Passing 0 as the new size to realloc is a murky corner of the standard. Implementations may:

  • Free ptr and return NULL (common, feels like free).
  • Return a unique pointer to zero-size memory that you must still call free on (some platforms, e.g. historical glibc behavior).

Because the two behaviors differ (one lets you free the result, the other means you already lost the block and free(NULL) is fine — but if you ignore the returned pointer you could leak or double-free), the standard calls it implementation-defined. The C committee’s guidance in C11/C17: don’t use realloc(ptr, 0) — it’s ambiguous and its semantics were a known defect. Use free(ptr) to release, and realloc only with a positive size.

The interview answer: implementation-defined — either frees and returns NULL, or returns a non-dereferenceable pointer that still needs free; it should be avoided.

6. What is a Memory Leak in C programming?

Answer: Allocating heap memory (via malloc/calloc/realloc) and losing the last reference to it without calling free, so it can never be reclaimed until the program exits.

A memory leak happens when you allocate memory, then drop the pointer to it (overwrite the pointer, lose it in an error path, let it go out of scope) before free is called. The block is still allocated — it consumes the process’s address space — but there’s no way to reach it to free it. Symptoms:

  • Program’s memory usage grows monotonically over time.
  • Long-running processes (servers, daemons) eventually exhaust memory and crash.

C has no garbage collector and no RAII — you must pair every malloc with a free on all code paths, including error paths. Tools like Valgrind and ASan detect leaks.

Note the distinction from the distractors: an out-of-bounds array access is a buffer overflow, reading an uninitialized local is uninitialized use, dereferencing NULL is a null dereference — none of those are leaks. The interview answer: heap memory allocated and never freed while its last pointer reference is lost, leaking address space until the program ends.

Answer:

Allocating heap memory (via malloc/calloc/realloc) and losing the last reference to it without calling free, so it can never be reclaimed until the program exits.

A memory leak happens when you allocate memory, then drop the pointer to it (overwrite the pointer, lose it in an error path, let it go out of scope) before free is called. The block is still allocated — it consumes the process’s address space — but there’s no way to reach it to free it. Symptoms:

  • Program’s memory usage grows monotonically over time.
  • Long-running processes (servers, daemons) eventually exhaust memory and crash.

C has no garbage collector and no RAII — you must pair every malloc with a free on all code paths, including error paths. Tools like Valgrind and ASan detect leaks.

Note the distinction from the distractors: an out-of-bounds array access is a buffer overflow, reading an uninitialized local is uninitialized use, dereferencing NULL is a null dereference — none of those are leaks. The interview answer: heap memory allocated and never freed while its last pointer reference is lost, leaking address space until the program ends.

7. What does the restrict qualifier tell the compiler when applied to a pointer argument (int * restrict p)?

Answer: That p is the only way to access the object it points to within its scope, enabling aggressive optimization without aliasing worries.

restrict is a promise from the programmer to the compiler: during the lifetime of p, no other pointer in scope accesses the same memory. This is a contract for aliasing — the situation where two pointers refer to the same object, which forces the compiler to be conservative (re-reading from memory after every store).

void copy(int *restrict dst, const int *restrict src, int n) {
    for (int i = 0; i < n; ++i) dst[i] = src[i];
}

With restrict, the compiler can cache src[i] in a register, reorder loads/stores, and apply vectorization, because dst can’t be overwriting src.

The danger: you must uphold the promise. If you pass overlapping buffers anyway, the behavior is undefined. Note restrict is a C keyword (C99+), not C++ (C++ adopted it via __restrict extensions and C++23’s restrict for this). The interview answer: restrict promises no other pointer aliases the same memory in scope, letting the compiler optimize freely.

Answer:

That p is the only way to access the object it points to within its scope, enabling aggressive optimization without aliasing worries.

restrict is a promise from the programmer to the compiler: during the lifetime of p, no other pointer in scope accesses the same memory. This is a contract for aliasing — the situation where two pointers refer to the same object, which forces the compiler to be conservative (re-reading from memory after every store).

void copy(int *restrict dst, const int *restrict src, int n) {
    for (int i = 0; i < n; ++i) dst[i] = src[i];
}

With restrict, the compiler can cache src[i] in a register, reorder loads/stores, and apply vectorization, because dst can’t be overwriting src.

The danger: you must uphold the promise. If you pass overlapping buffers anyway, the behavior is undefined. Note restrict is a C keyword (C99+), not C++ (C++ adopted it via __restrict extensions and C++23’s restrict for this). The interview answer: restrict promises no other pointer aliases the same memory in scope, letting the compiler optimize freely.

8. What is the output of the following pointer arithmetic code on a standard system?

int arr[] = {10, 20, 30, 40};
int *p = arr;
printf("%d", *(p + 2));

Output: 30.

Pointer arithmetic is scaled by the pointed-to type’s size. p + 2 advances by 2 * sizeof(int) — two elements, not two bytes. p starts at arr[0]; p + 2 points at arr[2], which is 30. *(p + 2) is equivalent to p[2] (or arr[2]). Output: 30.

Answer:

30.

Pointer arithmetic is scaled by the pointed-to type’s size. p + 2 advances by 2 * sizeof(int) — two elements, not two bytes. p starts at arr[0]; p + 2 points at arr[2], which is 30. *(p + 2) is equivalent to p[2] (or arr[2]). Output: 30.

9. What occurs when dereferencing a NULL pointer in C?

Answer: Undefined behavior — in practice, typically a crash / segmentation fault (SIGSEGV).

Dereferencing NULL (e.g., *ptr where ptr == NULL, or ptr->field) is undefined behavior per the C standard. On modern OSes with virtual memory, address 0 lies on an unmapped page, so the hardware raises a protection fault that the OS delivers as SIGSEGV, terminating the program.

C has no exceptions — nothing “catches” the fault like a C++ exception. Good practice is to check for NULL before dereferencing (especially on pointers returned by malloc, fopen, etc.). The interview answer: UB — almost always a SIGSEGV crash on OS-managed memory systems.

Answer:

Undefined behavior — in practice, typically a crash / segmentation fault (SIGSEGV).

Dereferencing NULL (e.g., *ptr where ptr == NULL, or ptr->field) is undefined behavior per the C standard. On modern OSes with virtual memory, address 0 lies on an unmapped page, so the hardware raises a protection fault that the OS delivers as SIGSEGV, terminating the program.

C has no exceptions — nothing “catches” the fault like a C++ exception. Good practice is to check for NULL before dereferencing (especially on pointers returned by malloc, fopen, etc.). The interview answer: UB — almost always a SIGSEGV crash on OS-managed memory systems.

10. What happens if you attempt to access an array index out of bounds (int a[5]; a[10] = 50;)?

Answer: Undefined behavior — the write goes into adjacent memory, potentially corrupting other data or crashing.

C performs no runtime bounds checking. a[10] on a 5-element array just computes the address &a[0] + 10*sizeof(int) and writes 50 there — into whatever memory happens to sit after the array (possibly other variables, a return address, or unmapped pages). Consequences are unpredictable:

  • Silently corrupting neighboring data (hard to debug).
  • Crashes when writing into invalid/unmapped memory.
  • Security vulnerabilities (buffer overflow attacks).

The compiler may warn at compile time for constant out-of-bounds indices, but for computed ones nothing catches it. Bounds checking is entirely the programmer’s job in C. The interview answer: UB — the write lands in adjacent memory, corrupting state or crashing.

Answer:

Undefined behavior — the write goes into adjacent memory, potentially corrupting other data or crashing.

C performs no runtime bounds checking. a[10] on a 5-element array just computes the address &a[0] + 10*sizeof(int) and writes 50 there — into whatever memory happens to sit after the array (possibly other variables, a return address, or unmapped pages). Consequences are unpredictable:

  • Silently corrupting neighboring data (hard to debug).
  • Crashes when writing into invalid/unmapped memory.
  • Security vulnerabilities (buffer overflow attacks).

The compiler may warn at compile time for constant out-of-bounds indices, but for computed ones nothing catches it. Bounds checking is entirely the programmer’s job in C. The interview answer: UB — the write lands in adjacent memory, corrupting state or crashing.

11. What is the difference between passing arguments by value versus passing by reference (using pointers) in C?

Answer: C only supports pass-by-value. To let a function modify a caller’s variable, you pass a copy of its address (a pointer), then dereference inside the function.

Every C argument is passed by value — a copy. When you pass a pointer, you’re passing a copy of the address value, not the variable itself:

void swap(int *a, int *b) {
    int t = *a; *a = *b; *b = t;   // dereference to reach caller's memory
}
int x = 1, y = 2;
swap(&x, &y);   // passes copies of addresses; swap modifies x,y via deref

The function can then reach into the caller’s memory through the address it received. That’s “pass by reference” in C in effect — but mechanically it’s still pass-by-value of a pointer.

(The one nuance: a const pointer int *const p would make the pointer itself immutable, but that’s separate.) The interview answer: C passes everything by value; pointers pass a copy of the address, letting functions modify caller variables via dereferencing.

Answer:

C only supports pass-by-value. To let a function modify a caller’s variable, you pass a copy of its address (a pointer), then dereference inside the function.

Every C argument is passed by value — a copy. When you pass a pointer, you’re passing a copy of the address value, not the variable itself:

void swap(int *a, int *b) {
    int t = *a; *a = *b; *b = t;   // dereference to reach caller's memory
}
int x = 1, y = 2;
swap(&x, &y);   // passes copies of addresses; swap modifies x,y via deref

The function can then reach into the caller’s memory through the address it received. That’s “pass by reference” in C in effect — but mechanically it’s still pass-by-value of a pointer.

(The one nuance: a const pointer int *const p would make the pointer itself immutable, but that’s separate.) The interview answer: C passes everything by value; pointers pass a copy of the address, letting functions modify caller variables via dereferencing.

12. What is the result of applying sizeof to a dereferenced pointer sizeof(*ptr) where double *ptr;?

Answer: The size of double8 bytes on standard systems.

*ptr has type double, so sizeof(*ptr) is sizeof(double) = 8 bytes. The clever part: sizeof does not evaluate its operand. It only needs the type of the expression, which is known at compile time. So ptr needn’t be initialized or even valid — sizeof(*ptr) never dereferences anything at runtime; there’s no crash.

This is a widely used idiom because it’s type-robust: sizeof(*ptr) stays correct even if you change ptr’s type. Compare sizeof(ptr) which would be the pointer size (8 on 64-bit). The interview answer: sizeof(*ptr) = sizeof(double) = 8, computed without evaluating the dereference.

Answer:

The size of double8 bytes on standard systems.

*ptr has type double, so sizeof(*ptr) is sizeof(double) = 8 bytes. The clever part: sizeof does not evaluate its operand. It only needs the type of the expression, which is known at compile time. So ptr needn’t be initialized or even valid — sizeof(*ptr) never dereferences anything at runtime; there’s no crash.

This is a widely used idiom because it’s type-robust: sizeof(*ptr) stays correct even if you change ptr’s type. Compare sizeof(ptr) which would be the pointer size (8 on 64-bit). The interview answer: sizeof(*ptr) = sizeof(double) = 8, computed without evaluating the dereference.

13. What happens when compiling int main() { int *p = malloc(10 * sizeof(int)); } without calling free(p) before exiting?

Answer: The OS reclaims the memory at process exit, but this is still a memory leak — a serious problem in long-running programs.

When the process terminates, the operating system reclaims all of its memory — so the program “gets away with it” on exit. That’s why leaks often go unnoticed in short-lived programs.

The real problem is long-running processes (servers, daemons, embedded systems):

  • Every leaked allocation stays allocated; the process’s memory footprint grows monotonically.
  • Eventually the process exhausts available memory and crashes or is killed by the OS.

So the interview point: OS cleanup at exit ≠ no leak. In anything long-lived, every malloc must be paired with a free. The interview answer: the OS frees process memory on exit, but it’s a genuine leak that degrades long-running programs over time.

Answer:

The OS reclaims the memory at process exit, but this is still a memory leak — a serious problem in long-running programs.

When the process terminates, the operating system reclaims all of its memory — so the program “gets away with it” on exit. That’s why leaks often go unnoticed in short-lived programs.

The real problem is long-running processes (servers, daemons, embedded systems):

  • Every leaked allocation stays allocated; the process’s memory footprint grows monotonically.
  • Eventually the process exhausts available memory and crashes or is killed by the OS.

So the interview point: OS cleanup at exit ≠ no leak. In anything long-lived, every malloc must be paired with a free. The interview answer: the OS frees process memory on exit, but it’s a genuine leak that degrades long-running programs over time.

14. What does function pointer declaration int (*func_ptr)(double) represent?

Answer: func_ptr is a pointer to a function that takes a double and returns an int.

C declaration reading rule — (*func_ptr) binds first (the parens are essential), so:

  • func_ptr is a pointer (*).
  • It points to a function.
  • That function takes a double parameter and returns an int.
int foo(double x);
int (*func_ptr)(double) = foo;   // func_ptr points to foo
func_ptr(3.14);                  // call through the pointer

Compare the trap: int *func_ptr(double) would be a function returning int* — the parens around *func_ptr are what distinguish “pointer to function” from “function returning pointer.” Function pointers enable callbacks, dispatch tables, and runtime-selectable behavior. The interview answer: a pointer to a function taking double and returning int.

Answer:

func_ptr is a pointer to a function that takes a double and returns an int.

C declaration reading rule — (*func_ptr) binds first (the parens are essential), so:

  • func_ptr is a pointer (*).
  • It points to a function.
  • That function takes a double parameter and returns an int.
int foo(double x);
int (*func_ptr)(double) = foo;   // func_ptr points to foo
func_ptr(3.14);                  // call through the pointer

Compare the trap: int *func_ptr(double) would be a function returning int* — the parens around *func_ptr are what distinguish “pointer to function” from “function returning pointer.” Function pointers enable callbacks, dispatch tables, and runtime-selectable behavior. The interview answer: a pointer to a function taking double and returning int.

15. What is the effect of applying const to a pointer variable declared as const int *ptr vs int * const ptr?

Answer: const int *ptr makes the pointed-to value read-only; int * const ptr makes the pointer itself read-only.

Read C declarations right-to-left:

  • const int *ptr → “ptr is a pointer to const int” — you can’t modify the integer through ptr, but you can point ptr elsewhere.
  • int * const ptr → “ptr is a const pointer to int” — you can modify the integer, but ptr can’t be reassigned to a different address.
const int *a;   // *a = 5; ERROR   a = &other; OK
int * const b;  // *b = 5; OK       b = &other; ERROR

There’s also const int * const c — both value and pointer read-only. Mnemonic: const applies to what’s immediately to its left (or right if nothing’s left). The interview answer: const int * protects the value; int * const protects the pointer variable.

Answer:

const int *ptr makes the pointed-to value read-only; int * const ptr makes the pointer itself read-only.

Read C declarations right-to-left:

  • const int *ptr → “ptr is a pointer to const int” — you can’t modify the integer through ptr, but you can point ptr elsewhere.
  • int * const ptr → “ptr is a const pointer to int” — you can modify the integer, but ptr can’t be reassigned to a different address.
const int *a;   // *a = 5; ERROR   a = &other; OK
int * const b;  // *b = 5; OK       b = &other; ERROR

There’s also const int * const c — both value and pointer read-only. Mnemonic: const applies to what’s immediately to its left (or right if nothing’s left). The interview answer: const int * protects the value; int * const protects the pointer variable.

16. What is the evaluation result of evaluating an expression containing sizeof(i++) where int i = 5;?

Answer: i remains 5sizeof is a compile-time operator that does not evaluate its operand (except for VLA operands).

sizeof(expr) only needs the type of the expression; it doesn’t run it. So sizeof(i++):

  • Computes the size of int (the type of i++), typically 4.
  • Never executes the incrementi stays 5.

The only exception: if the operand is a variable-length array (VLA), sizeof must evaluate parts of it (the size expression) at runtime. For everything else, no side effects occur. This is why sizeof(*ptr) is safe even with an uninitialized pointer. The interview answer: i stays 5 — sizeof doesn’t evaluate its operand (barring VLAs).

Answer:

i remains 5sizeof is a compile-time operator that does not evaluate its operand (except for VLA operands).

sizeof(expr) only needs the type of the expression; it doesn’t run it. So sizeof(i++):

  • Computes the size of int (the type of i++), typically 4.
  • Never executes the incrementi stays 5.

The only exception: if the operand is a variable-length array (VLA), sizeof must evaluate parts of it (the size expression) at runtime. For everything else, no side effects occur. This is why sizeof(*ptr) is safe even with an uninitialized pointer. The interview answer: i stays 5 — sizeof doesn’t evaluate its operand (barring VLAs).

17. What is the outcome of int *ptr = (int*)malloc(0);?

Answer: Implementation-defined — either NULL or a unique non-null pointer, and in both cases the result must still (or may) be passed to free().

Requesting 0 bytes from malloc is legal but the behavior is implementation-defined:

  • Some implementations return NULL (no allocation).
  • Others return a unique non-null pointer to a zero-size region.

free() on either result is safe: free(NULL) is a no-op, and the non-null pointer is a valid malloc result that must be freed to avoid a leak. So regardless of which the platform chooses, free(ptr) is correct. What you must not do is dereference the result as if it had room — a 0-byte region isn’t usable storage. The interview answer: returns NULL or a unique non-null pointer (implementation-defined); either can safely be passed to free.

Answer:

Implementation-defined — either NULL or a unique non-null pointer, and in both cases the result must still (or may) be passed to free().

Requesting 0 bytes from malloc is legal but the behavior is implementation-defined:

  • Some implementations return NULL (no allocation).
  • Others return a unique non-null pointer to a zero-size region.

free() on either result is safe: free(NULL) is a no-op, and the non-null pointer is a valid malloc result that must be freed to avoid a leak. So regardless of which the platform chooses, free(ptr) is correct. What you must not do is dereference the result as if it had room — a 0-byte region isn’t usable storage. The interview answer: returns NULL or a unique non-null pointer (implementation-defined); either can safely be passed to free.

18. What is a “Dangling Pointer”?

Answer: A pointer that still holds an address after the memory it points to has been freed (or has gone out of scope) — dereferencing it is undefined behavior.

A dangling pointer is stale: it points to memory that no longer belongs to the program’s valid state. How pointers become dangling:

  • Heap: free(ptr) but ptr keeps the old address.
  • Stack: returning a pointer to a local variable — the frame pops and the memory is reused.
  • Reallocation: realloc moves the block; old pointers still hold the old address.
int *p = malloc(sizeof(int));
free(p);        // p now dangles
*p = 5;         // UB — use-after-free

Dereferencing a dangling pointer is undefined behavior (use-after-free). Prevention: set pointers to NULL after free, prefer RAII-style ownership in C++, and be careful with realloc/scope. The interview answer: a pointer referencing memory that has been deallocated; using it is UB.

Answer:

A pointer that still holds an address after the memory it points to has been freed (or has gone out of scope) — dereferencing it is undefined behavior.

A dangling pointer is stale: it points to memory that no longer belongs to the program’s valid state. How pointers become dangling:

  • Heap: free(ptr) but ptr keeps the old address.
  • Stack: returning a pointer to a local variable — the frame pops and the memory is reused.
  • Reallocation: realloc moves the block; old pointers still hold the old address.
int *p = malloc(sizeof(int));
free(p);        // p now dangles
*p = 5;         // UB — use-after-free

Dereferencing a dangling pointer is undefined behavior (use-after-free). Prevention: set pointers to NULL after free, prefer RAII-style ownership in C++, and be careful with realloc/scope. The interview answer: a pointer referencing memory that has been deallocated; using it is UB.

19. What occurs if calloc() fails to allocate requested memory blocks?

Answer: It returns NULL.

All C dynamic-allocation functions — malloc, calloc, realloc — signal failure by returning NULL; none of them throw or abort. calloc returns NULL when it can’t allocate (or when num * size overflows, in which case it also returns NULL).

So the caller must check the result:

int *p = calloc(n, sizeof(int));
if (p == NULL) {
    /* handle allocation failure */
}

This is why every allocation in C should be followed by a null check. The interview answer: calloc returns NULL on failure, which the caller must check.

Answer:

It returns NULL.

All C dynamic-allocation functions — malloc, calloc, realloc — signal failure by returning NULL; none of them throw or abort. calloc returns NULL when it can’t allocate (or when num * size overflows, in which case it also returns NULL).

So the caller must check the result:

int *p = calloc(n, sizeof(int));
if (p == NULL) {
    /* handle allocation failure */
}

This is why every allocation in C should be followed by a null check. The interview answer: calloc returns NULL on failure, which the caller must check.

20. What is the result of using sizeof on a variable length array (VLA) parameter inside a function void foo(int n, int arr[n])?

Answer: sizeof(arr) gives the pointer size (e.g., 8 bytes on 64-bit), because the VLA parameter decays to a pointer.

A VLA parameter (int arr[n]) is not a real VLA — function parameters of array type always decay to pointers. void foo(int n, int arr[n]) is adjusted to void foo(int n, int *arr). Inside foo, arr is a pointer, so sizeof(arr) is the pointer size (8 on 64-bit), not n × sizeof(int).

True VLAs (where sizeof does evaluate at runtime) only exist for local/block-scope arrays with non-constant size:

void bar(int n) {
    int local[n];           // real VLA
    sizeof(local);          // runtime n * sizeof(int)
}

The interview answer: pointer size — VLA parameters decay to int *, so sizeof(arr) is 8 bytes, not the array’s byte size.

Answer:

sizeof(arr) gives the pointer size (e.g., 8 bytes on 64-bit), because the VLA parameter decays to a pointer.

A VLA parameter (int arr[n]) is not a real VLA — function parameters of array type always decay to pointers. void foo(int n, int arr[n]) is adjusted to void foo(int n, int *arr). Inside foo, arr is a pointer, so sizeof(arr) is the pointer size (8 on 64-bit), not n × sizeof(int).

True VLAs (where sizeof does evaluate at runtime) only exist for local/block-scope arrays with non-constant size:

void bar(int n) {
    int local[n];           // real VLA
    sizeof(local);          // runtime n * sizeof(int)
}

The interview answer: pointer size — VLA parameters decay to int *, so sizeof(arr) is 8 bytes, not the array’s byte size.

21. What happens if malloc() is requested to allocate memory larger than available system memory?

Answer: malloc() fails and returns NULL — the program does not crash automatically.

When the allocator can’t satisfy the request (system memory exhausted, or the request size itself is absurd), malloc returns NULL rather than crashing or throwing. What happens next is up to the caller:

  • A well-written program checks for NULL and handles the failure gracefully.
  • A careless program dereferences the NULL → crash (segmentation fault).

Note an extra wrinkle: on many OSes, malloc can succeed initially thanks to overcommit (memory is committed lazily on touch), and the failure shows up as an OOM kill or fault later. But at the API level, exhaustion → NULL. The interview answer: malloc returns NULL to signal allocation failure; the caller must check it.

Answer:

malloc() fails and returns NULL — the program does not crash automatically.

When the allocator can’t satisfy the request (system memory exhausted, or the request size itself is absurd), malloc returns NULL rather than crashing or throwing. What happens next is up to the caller:

  • A well-written program checks for NULL and handles the failure gracefully.
  • A careless program dereferences the NULL → crash (segmentation fault).

Note an extra wrinkle: on many OSes, malloc can succeed initially thanks to overcommit (memory is committed lazily on touch), and the failure shows up as an OOM kill or fault later. But at the API level, exhaustion → NULL. The interview answer: malloc returns NULL to signal allocation failure; the caller must check it.

My Private Notes

Notes are auto-saved locally to this device.