Menu

Earn Premium with Referrals

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

See how it works and start inviting friends.

Top 50 - Part 1
C

Top 50 - Part 1

Practice the first 15 questions from a comprehensive set of 50 important C programming interview questions.

1. What is the behavior of reading a union member different from the one most recently written to?

Answer: It reinterprets the raw bit pattern as the new member’s type — type-punning, which C explicitly permits.

Unlike C++, C allows reading a union member other than the one last written — a feature called type punning. The stored bytes are reinterpreted as the target member’s type. This is only meaningful when the members are the same size (and alignment-compatible):

union { float f; uint32_t i; } u;
u.f = 1.5f;
printf("%08x", u.i);   // raw IEEE-754 bits of 1.5f, as an unsigned int

This is the classic low-level idiom for inspecting the binary representation of a value (e.g., floating-point bit manipulation) without memcpy or pointer-cast aliasing games. (C23 formalized and strengthened these guarantees.) Caveat: the interpretation depends on the representation — it’s a deliberate bit-level view, not a “safe conversion.” The interview answer: the stored bit pattern is read as the new member’s type (type-punning), supported when sizes/alignment match.

2. What is the purpose of the atexit() function in C?

Answer: It registers cleanup functions that run automatically when the program exits normally (via exit() or returning from main()).

atexit(func) registers func to be called at normal program termination. Key behaviors:

  • Runs on exit() calls or when main returns (normal termination only — not on abort() or a crash).
  • Functions run in reverse order of registration (LIFO — the last registered runs first).
  • Called with no arguments; multiple registrations are allowed (commonly up to 32 or more).
void cleanup() { close_log(); }
int main() {
    atexit(cleanup);
    /* ... */
    return 0;   // cleanup() runs here
}

Use case: centralized resource cleanup (closing files, flushing buffers) without scattering it through every exit path. The interview answer: atexit registers functions to run automatically on normal termination via exit()/main return, in LIFO order.

3. What does strtok() modify during string tokenization execution?

Answer: It modifies the input string in place, replacing each delimiter with a '\0' terminator and returning pointers to the resulting tokens.

strtok tokenizes a string on the fly. Its mechanics are unusual:

  • The first call takes the source string; subsequent calls pass NULL to continue from where the previous call left off (the function keeps internal static state).
  • Each delimiter character in the input is overwritten with '\0', so the original string is mutated.
  • It returns pointers into that same buffer — no new memory is allocated.
char s[] = "apple,banana,cherry";
char *tok = strtok(s, ",");         // "apple"
tok = strtok(NULL, ",");            // "banana"
tok = strtok(NULL, ",");            // "cherry"

Consequences: the source must be a mutable array (not a string literal — writing to literals is UB), and the internal static state makes strtok not thread-safe (strtok_r is the reentrant version). The interview answer: strtok mutates the source in place, writing '\0' over delimiters and returning token pointers into the same buffer.

4. What is the memory location where uninitialized global and static variables are stored?

Answer: The BSS segment (Block Started by Symbol).

A program’s memory layout separates data by initialization state:

  • Text/Code — executable instructions.
  • Data segmentinitialized globals/statics.
  • BSSuninitialized globals and statics. The OS zeroes BSS before program start, so uninitialized globals reliably read as 0.
  • Heap — dynamic allocation (grows toward higher addresses).
  • Stack — function call frames (grows toward lower addresses).
int g;            // BSS — zeroed before main
static int s;     // BSS — zeroed before main

That’s why globals/statics are guaranteed 0 while automatic (local) variables are garbage. The interview answer: uninitialized globals/statics live in BSS, which the OS zeroes before execution starts.

5. What is the purpose of <stdbool.h> introduced in C99?

Answer: It defines bool (an alias for the built-in _Bool), plus true (1) and false (0).

C99 introduced the native _Bool type. <stdbool.h> provides the friendly macros:

  • bool_Bool
  • true1
  • false0
#include <stdbool.h>
bool isReady = true;

C’s _Bool is a real type: it stores only 0 or 1, and any non-zero value assigned to it converts to 1. So bool b = 42; yields b == 1. Before C99/C23 code had to fake booleans with int/typedef int bool;. The interview answer: defines bool (=_Bool), true (1), and false (0).

6. What is the consequence of modifying a variable passed into a signal handler in standard C?

Answer: The handler may only safely write to volatile sig_atomic_t (or lock-free atomic) variables; touching anything else is undefined behavior due to async interruption.

Signal handlers interrupt the program asynchronously — at any instruction, in any state. Writing to an ordinary variable from a handler races with whatever the interrupted code was doing (it could be mid-way through a multi-byte update), producing corrupted or partial values. The C standard therefore only guarantees signal safety for volatile sig_atomic_t — an integer type that’s always read/written in a single atomic step:

volatile sig_atomic_t flag = 0;
void handler(int sig) { flag = 1; }   // OK

(A handler must also only call async-signal-safe functions — not printf, malloc, etc.) If you need to modify arbitrary data, have the handler set a flag and let the main flow do the real work. The interview answer: only volatile sig_atomic_t (or lock-free atomic) variables are safe to write from a signal handler; other accesses are UB.

7. What is the value of EOF defined in <stdio.h>?

Answer: A negative integer constant (typically -1) used to signal end-of-file or a read error.

EOF (End-Of-File) is an integer macro, conventionally -1. It’s returned by character-reading functions like fgetc/getchar to say “there’s no more data” (or an error occurred):

int ch;
while ((ch = getchar()) != EOF) { putchar(ch); }

Note ch is an int, not char — that’s deliberate. char may be unsigned (values 0–255), which could never hold -1; int captures both the byte value and EOF. Also EOF is a value indicator, distinct from feof() which checks the stream’s end-of-file status flag. The interview answer: a negative int constant, typically -1, returned by stream-reading functions to indicate EOF or error.

8. How does snprintf() protect against buffer overflow compared to sprintf()?

Answer: snprintf(buf, size, fmt, ...) caps output at size - 1 bytes and always appends a '\0'; sprintf() writes without limit.

sprintf(buf, fmt, ...) formats and copies the entire result into buf with no bounds check — a long formatted result overflows the buffer (the classic overflow vector).

snprintf takes an explicit buffer size and guarantees:

  • At most size - 1 bytes of formatted output are written (room for the null terminator).
  • The result is always null-terminated (unless size is 0).
char buf[64];
snprintf(buf, sizeof(buf), "%s", long_string);   // safely truncated, terminated

The return value (what would have been written) lets you detect truncation. Modern guidance: prefer snprintf (and its sibling vsnprintf) over sprintf everywhere. The interview answer: snprintf writes at most size - 1 bytes plus a null terminator, preventing overruns.

9. 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.

10. 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.

11. 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.

12. What does the expression (void)var; do in C source files?

Answer: It suppresses “unused variable” compiler warnings intentionally, without emitting any runtime code.

Writing (void)var; as a statement casts var to void and discards it. The compiler sees the variable as “used” (it appears in an expression), so it won’t warn about it being unused — but casting to void means “I’m deliberately ignoring this,” and no machine code is generated for it.

Typical uses:

  • Function parameters that are intentionally unused (e.g., callback signatures where you don’t need every argument).
  • Suppressing warnings in macro-generated code.
  • Documenting that a variable is intentionally ignored.

It’s purely a compile-time signal to the compiler and a readability note to humans. The interview answer: an intentional no-op that silences unused-variable warnings without runtime cost.

13. What is the output of printf(“%d”, 012);?

Answer: 10.

In C, an integer literal with a leading 0 is interpreted as octal (base 8) — not decimal. 012 means octal 12:

012₈ = 1×8¹ + 2×8⁰ = 8 + 2 = 10

So printf("%d", 012) prints 10 (in decimal). The 0x prefix means hex, leading 0 means octal, and a plain nonzero digit means decimal. This is a classic trick/trap — writing 012 when you meant twelve gives you ten. (C23 deprecates legacy octal 0-prefixed literals in favor of explicit 0o for clarity.) The interview answer: 10012 is an octal literal equal to decimal 10.

14. What is the fundamental requirement when using memcpy() versus memmove()?

Answer: memcpy() requires the source and destination to not overlap; memmove() safely handles overlapping regions.

  • memcpy is the fast, optimized copy — but it assumes non-overlapping buffers. If the regions overlap, the result is undefined behavior (the copy can read already-overwritten bytes).
  • memmove guarantees correct results even when the regions overlap, by copying as if through an intermediate buffer (internally it detects direction and copies forward/backward as needed). It may be marginally slower.
memcpy(dst, src, n);    // UB if dst/src overlap
memmove(dst, src, n);   // safe with overlap

The canonical overlap case is shifting array elements: memmove(arr+1, arr, n-1). Rule: use memmove whenever overlap is possible, memcpy when you’re certain regions are disjoint. The interview answer: memcpy requires no overlap (UB otherwise); memmove handles overlap safely.

15. What is the evaluation result of binary operator ~0 on an 8-bit unsigned variable?

Answer: 255 (0xFF).

~ is bitwise NOT: it flips every bit. On an 8-bit value:

~00000000₂ = 11111111₂ = 255

For an unsigned char, all eight bits are flipped to 1 → 255.

The -1 distractor applies if the operand is a signed int: ~0 on a 32-bit int gives 0xFFFFFFFF, which as a signed int is -1. The result depends on the operand’s type — the question specifies unsigned 8-bit, so the answer is 255. The interview answer: 255 (0xFF) for an 8-bit unsigned value.

My Private Notes

Notes are auto-saved locally to this device.