Menu

Earn Premium with Referrals

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

See how it works and start inviting friends.

Operators, Stdlib & I/O
C

Operators, Stdlib & I/O

Practice 27 questions covering operators, standard library functions, input/output, precedence, expressions, and common C pitfalls.

1. What is the behavior of bitwise left shift x << n when x is a signed negative integer in C?

Answer: Undefined behavior (prior to C23) — the C standard does not define left-shifting a negative signed value.

In C standards before C23, the result of x << n is well-defined only when x is non-negative and the result fits in the type. Left-shifting a signed negative value violates that rule, so the behavior is undefined — the program may do anything.

(The C23 standard changed this: it now defines E1 << E2 as the value E1 × 2^E2, even for negative E1, as long as it’s representable. But for older standards and the classic interview answer: it’s UB.)

The reason is historical: signed integer representation was implementation-defined (two’s complement is universal today, but the standard had to cover sign-magnitude and ones’ complement). The interview answer: UB under pre-C23 rules; C23 makes it well-defined for representable results.

Answer:

Undefined behavior (prior to C23) — the C standard does not define left-shifting a negative signed value.

In C standards before C23, the result of x << n is well-defined only when x is non-negative and the result fits in the type. Left-shifting a signed negative value violates that rule, so the behavior is undefined — the program may do anything.

(The C23 standard changed this: it now defines E1 << E2 as the value E1 × 2^E2, even for negative E1, as long as it’s representable. But for older standards and the classic interview answer: it’s UB.)

The reason is historical: signed integer representation was implementation-defined (two’s complement is universal today, but the standard had to cover sign-magnitude and ones’ complement). The interview answer: UB under pre-C23 rules; C23 makes it well-defined for representable results.

2. What is the evaluation result of 5 / 2 in C?

Answer: 2.

When both operands are integers, / performs integer division: the result is the quotient with the fractional part truncated toward zero. 5 / 2 = 2 (the .5 is dropped).

If you want 2.5, at least one operand must be floating-point: 5.0 / 2, 5 / 2.0, or 5.0 / 2.0 all give 2.5. (Also watch truncation direction for negatives — C truncates toward zero since C99, so -5 / 2 is -2, not -3.) The interview answer: 2 — integer division truncates the fractional part.

Answer:

2.

When both operands are integers, / performs integer division: the result is the quotient with the fractional part truncated toward zero. 5 / 2 = 2 (the .5 is dropped).

If you want 2.5, at least one operand must be floating-point: 5.0 / 2, 5 / 2.0, or 5.0 / 2.0 all give 2.5. (Also watch truncation direction for negatives — C truncates toward zero since C99, so -5 / 2 is -2, not -3.) The interview answer: 2 — integer division truncates the fractional part.

3. What is the standard return value of main() to signal successful execution to the Operating System?

Answer: 0 (or EXIT_SUCCESS).

main() returns an int status to the OS. Returning 0 (or EXIT_SUCCESS from <stdlib.h>) signals success. A non-zero value signals failure or an error code — shells check this via $?.

EXIT_SUCCESS is usually 0 and EXIT_FAILURE is typically 1 (both defined in <stdlib.h>), giving you named constants instead of magic numbers. The interview answer: return 0 or EXIT_SUCCESS to signal successful termination.

Answer:

0 (or EXIT_SUCCESS).

main() returns an int status to the OS. Returning 0 (or EXIT_SUCCESS from <stdlib.h>) signals success. A non-zero value signals failure or an error code — shells check this via $?.

EXIT_SUCCESS is usually 0 and EXIT_FAILURE is typically 1 (both defined in <stdlib.h>), giving you named constants instead of magic numbers. The interview answer: return 0 or EXIT_SUCCESS to signal successful termination.

4. What does setjmp and longjmp provide in C programming?

Answer: Non-local jumps — a low-level exception-handling mechanism that can jump back across multiple function call frames.

setjmp and longjmp (from <setjmp.h>) are the C way to “throw” across stack frames without normal returns:

  • setjmp(buf) saves the execution environment (stack pointer, instruction pointer, registers, signal mask) into a jmp_buf, and returns 0 the first time.
  • longjmp(buf, value) restores that saved environment and jumps execution back to the setjmp point — unwinding any intermediate function frames.
jmp_buf env;
if (setjmp(env) == 0) {
    do_work();          // deep call chain
} else {
    // longjmp landed here, value != 0
    handle_error();
}

It’s the classic error-handling idiom for C (used by old C codebases for exception-like flow). Caveats: local automatic variables modified between setjmp and longjmp may be indeterminate unless volatile; you can’t jump past the frame containing setjmp (that frame must still be alive). The interview answer: non-local jumps for exception-style control flow across function call frames.

Answer:

Non-local jumps — a low-level exception-handling mechanism that can jump back across multiple function call frames.

setjmp and longjmp (from <setjmp.h>) are the C way to “throw” across stack frames without normal returns:

  • setjmp(buf) saves the execution environment (stack pointer, instruction pointer, registers, signal mask) into a jmp_buf, and returns 0 the first time.
  • longjmp(buf, value) restores that saved environment and jumps execution back to the setjmp point — unwinding any intermediate function frames.
jmp_buf env;
if (setjmp(env) == 0) {
    do_work();          // deep call chain
} else {
    // longjmp landed here, value != 0
    handle_error();
}

It’s the classic error-handling idiom for C (used by old C codebases for exception-like flow). Caveats: local automatic variables modified between setjmp and longjmp may be indeterminate unless volatile; you can’t jump past the frame containing setjmp (that frame must still be alive). The interview answer: non-local jumps for exception-style control flow across function call frames.

5. What is the function of fflush(stdout)?

Answer: It forces pending buffered output in the stdout stream to be written out immediately to the terminal or file.

Standard I/O (stdio) buffers output for efficiency — printf writes into an internal buffer that’s flushed periodically (on newline for line-buffered terminals, on buffer-full, or at program exit). fflush(stdout) forces any buffered bytes out right now.

When you need it:

  • Interleaving printf with fprintf(stderr, ...) (stderr is unbuffered, so ordering matters) — flush stdout so the relative order is correct.
  • Prompting for input before reading: printf("Enter: "); fflush(stdout); ensures the prompt appears before the user types.
  • Crash-prone programs: flush critical output so it survives an abnormal exit.

The interview answer: fflush(stdout) pushes buffered stdout data to the underlying output immediately.

Answer:

It forces pending buffered output in the stdout stream to be written out immediately to the terminal or file.

Standard I/O (stdio) buffers output for efficiency — printf writes into an internal buffer that’s flushed periodically (on newline for line-buffered terminals, on buffer-full, or at program exit). fflush(stdout) forces any buffered bytes out right now.

When you need it:

  • Interleaving printf with fprintf(stderr, ...) (stderr is unbuffered, so ordering matters) — flush stdout so the relative order is correct.
  • Prompting for input before reading: printf("Enter: "); fflush(stdout); ensures the prompt appears before the user types.
  • Crash-prone programs: flush critical output so it survives an abnormal exit.

The interview answer: fflush(stdout) pushes buffered stdout data to the underlying output immediately.

6. What is the evaluation result of bitwise operation 5 & 3 in binary (0101 & 0011)?

Answer: 1.

Bitwise AND (&) compares bits positionally — each output bit is 1 only if both input bits are 1:

  0101   (5)
& 0011   (3)
------
  0001   (1)

So 5 & 3 = 1. (& is bitwise AND; don’t confuse with &&, the logical AND.) The interview answer: 0001₂ = 1.

Answer:

1.

Bitwise AND (&) compares bits positionally — each output bit is 1 only if both input bits are 1:

  0101   (5)
& 0011   (3)
------
  0001   (1)

So 5 & 3 = 1. (& is bitwise AND; don’t confuse with &&, the logical AND.) The interview answer: 0001₂ = 1.

7. What is the effect of invoking abort() in a C program?

Answer: It raises SIGABRT and terminates the process immediately — without running atexit() handlers or flushing stdio buffers.

abort() (from <stdlib.h>) abnormally terminates the program:

  • Raises the SIGABRT signal; unless caught, the process dies immediately.
  • Skips atexit() cleanup handlers (the functions registered to run at normal exit).
  • Skips stdio buffer flushing — buffered output may be lost.

It’s the hard-kill for catastrophic/uncorrectable states where cleanup can’t be trusted. Compare exit(code), which does run atexit handlers and flushes streams before terminating normally. The interview answer: immediate SIGABRT termination, bypassing atexit handlers and stdio flushing.

Answer:

It raises SIGABRT and terminates the process immediately — without running atexit() handlers or flushing stdio buffers.

abort() (from <stdlib.h>) abnormally terminates the program:

  • Raises the SIGABRT signal; unless caught, the process dies immediately.
  • Skips atexit() cleanup handlers (the functions registered to run at normal exit).
  • Skips stdio buffer flushing — buffered output may be lost.

It’s the hard-kill for catastrophic/uncorrectable states where cleanup can’t be trusted. Compare exit(code), which does run atexit handlers and flushes streams before terminating normally. The interview answer: immediate SIGABRT termination, bypassing atexit handlers and stdio flushing.

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

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.

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

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.

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

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.

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

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.

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

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.

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

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.

14. What does the string formatting specifier %p expect in printf()?

Answer: A pointer cast to void*, printed as a memory address in hexadecimal.

%p is the pointer-formatting specifier. The argument should be a pointer (properly cast to void* per the standard):

int x = 42;
printf("%p", (void *)&x);   // e.g., 0x7ffeefbff5c0

Output format is implementation-defined but universally hexadecimal with a 0x prefix. Notes: the argument type is a pointer, not a string or integer — passing the wrong type for %p is a format-string bug (undefined behavior). Also, %p prints an address, not the value at the address — use %d/%c/etc. for the pointed-to data. The interview answer: a void* pointer argument, printed as a hexadecimal memory address.

Answer:

A pointer cast to void*, printed as a memory address in hexadecimal.

%p is the pointer-formatting specifier. The argument should be a pointer (properly cast to void* per the standard):

int x = 42;
printf("%p", (void *)&x);   // e.g., 0x7ffeefbff5c0

Output format is implementation-defined but universally hexadecimal with a 0x prefix. Notes: the argument type is a pointer, not a string or integer — passing the wrong type for %p is a format-string bug (undefined behavior). Also, %p prints an address, not the value at the address — use %d/%c/etc. for the pointed-to data. The interview answer: a void* pointer argument, printed as a hexadecimal memory address.

15. What does the C library function system(“command”) execute?

Answer: It passes the string to the host environment’s command processor (/bin/sh or cmd.exe) for execution.

system("cmd") spawns the OS command shell to run the given command, waits for it to finish, and returns its exit status:

system("ls -l");       // runs in the shell, like typing it
system("mkdir /tmp/x");

Details:

  • The exact shell and behavior are platform-defined (POSIX: /bin/sh -c, Windows: cmd.exe /c).
  • The return value is the command’s termination status (interpretable via WEXITSTATUS), or -1 if the shell couldn’t run.
  • It’s blocking — the caller waits for completion.
  • Security caveat: the string is interpreted by the shell, so unsanitized user input in system() is a command-injection vulnerability — prefer direct function calls or exec-family APIs where possible.

The interview answer: it hands the string to the platform’s command shell (/bin/sh/cmd.exe) to execute, returning the exit status.

Answer:

It passes the string to the host environment’s command processor (/bin/sh or cmd.exe) for execution.

system("cmd") spawns the OS command shell to run the given command, waits for it to finish, and returns its exit status:

system("ls -l");       // runs in the shell, like typing it
system("mkdir /tmp/x");

Details:

  • The exact shell and behavior are platform-defined (POSIX: /bin/sh -c, Windows: cmd.exe /c).
  • The return value is the command’s termination status (interpretable via WEXITSTATUS), or -1 if the shell couldn’t run.
  • It’s blocking — the caller waits for completion.
  • Security caveat: the string is interpreted by the shell, so unsanitized user input in system() is a command-injection vulnerability — prefer direct function calls or exec-family APIs where possible.

The interview answer: it hands the string to the platform’s command shell (/bin/sh/cmd.exe) to execute, returning the exit status.

16. What is the evaluation result of applying logical negation !5 in C?

Answer: 0.

C’s logical operators treat any non-zero value as true. ! (logical NOT) flips truth to false and false to truth:

  • !5 — 5 is non-zero (true) → NOT true → 0 (false).
  • !0 — 0 is false → 1 (true).

So !5 evaluates to the integer 0. (It’s a boolean result represented as int in C: always 0 or 1.) The interview answer: 0 — logical NOT of any non-zero value is false.

Answer:

0.

C’s logical operators treat any non-zero value as true. ! (logical NOT) flips truth to false and false to truth:

  • !5 — 5 is non-zero (true) → NOT true → 0 (false).
  • !0 — 0 is false → 1 (true).

So !5 evaluates to the integer 0. (It’s a boolean result represented as int in C: always 0 or 1.) The interview answer: 0 — logical NOT of any non-zero value is false.

17. What is the purpose of standard library function qsort() in <stdlib.h>?

Answer: It sorts an array of arbitrary elements in place using a caller-supplied comparison callback.

qsort(base, num, size, compar):

  • base — pointer to the array start.
  • num — number of elements.
  • size — byte size of each element.
  • compar — function returning <0, 0, or >0 for the ordering of two elements.
int cmp(const void *a, const void *b) {
    return (*(int *)a) - (*(int *)b);
}
qsort(arr, n, sizeof(int), cmp);

It works on any element type because it only ever moves bytes of size length and asks compar for ordering. The comparator receives const void* pointers, which you cast to the element type. (Worst case is O(n log n); the name is historical — “quick sort.”) The interview answer: in-place array sort of arbitrary element types driven by a user-provided comparison callback.

Answer:

It sorts an array of arbitrary elements in place using a caller-supplied comparison callback.

qsort(base, num, size, compar):

  • base — pointer to the array start.
  • num — number of elements.
  • size — byte size of each element.
  • compar — function returning <0, 0, or >0 for the ordering of two elements.
int cmp(const void *a, const void *b) {
    return (*(int *)a) - (*(int *)b);
}
qsort(arr, n, sizeof(int), cmp);

It works on any element type because it only ever moves bytes of size length and asks compar for ordering. The comparator receives const void* pointers, which you cast to the element type. (Worst case is O(n log n); the name is historical — “quick sort.”) The interview answer: in-place array sort of arbitrary element types driven by a user-provided comparison callback.

18. What is the function of clock() in <time.h>?

Answer: It returns the processor (CPU) time consumed by the program since it started, as a clock_t value convertible to seconds via CLOCKS_PER_SEC.

clock() measures CPU time — the amount of processor time the process has used (across all threads), not wall-clock elapsed time. It differs from wall-clock when the program sleeps or waits for I/O (that time isn’t “processing”).

clock_t start = clock();
/* ... work ... */
double secs = (double)(clock() - start) / CLOCKS_PER_SEC;

If the value is (clock_t)(-1), the time is unavailable. For wall-clock time, you’d use time(), gettimeofday, or clock_gettime(CLOCK_MONOTONIC) instead. The interview answer: CPU time since process start (a clock_t), divided by CLOCKS_PER_SEC to get seconds.

Answer:

It returns the processor (CPU) time consumed by the program since it started, as a clock_t value convertible to seconds via CLOCKS_PER_SEC.

clock() measures CPU time — the amount of processor time the process has used (across all threads), not wall-clock elapsed time. It differs from wall-clock when the program sleeps or waits for I/O (that time isn’t “processing”).

clock_t start = clock();
/* ... work ... */
double secs = (double)(clock() - start) / CLOCKS_PER_SEC;

If the value is (clock_t)(-1), the time is unavailable. For wall-clock time, you’d use time(), gettimeofday, or clock_gettime(CLOCK_MONOTONIC) instead. The interview answer: CPU time since process start (a clock_t), divided by CLOCKS_PER_SEC to get seconds.

19. What is the size of char type guaranteed by the C standard on all compliant platforms?

Answer: Exactly 1 byte (a byte being CHAR_BIT bits, typically 8).

By definition, sizeof(char) is 1 on every conforming C implementation — it’s the unit in which all other sizes are measured. The subtlety: the number of bits in a byte is CHAR_BIT, which the standard allows to vary (usually 8; older exotic DSPs had more). So:

  • sizeof(char) == 1 always.
  • CHAR_BIT gives how many bits that byte holds (typically 8, could be 16, 32, etc. on unusual platforms).

char, signed char, unsigned char all occupy 1 byte each (but are three distinct types). The interview answer: exactly 1 byte (CHAR_BIT bits, normally 8) on all compliant platforms.

Answer:

Exactly 1 byte (a byte being CHAR_BIT bits, typically 8).

By definition, sizeof(char) is 1 on every conforming C implementation — it’s the unit in which all other sizes are measured. The subtlety: the number of bits in a byte is CHAR_BIT, which the standard allows to vary (usually 8; older exotic DSPs had more). So:

  • sizeof(char) == 1 always.
  • CHAR_BIT gives how many bits that byte holds (typically 8, could be 16, 32, etc. on unusual platforms).

char, signed char, unsigned char all occupy 1 byte each (but are three distinct types). The interview answer: exactly 1 byte (CHAR_BIT bits, normally 8) on all compliant platforms.

20. What does the file mode string “a+” specify when using fopen()?

Answer: Open for both reading and appending — existing content is preserved and all writes go to the end of the file.

"a+" (append + read/update) means:

  • The file is opened for reading and writing.
  • Existing content is preserved (unlike "w" which truncates).
  • The file is created if it doesn’t exist.
  • All write operations are forced to the end of the file — even if you fseek the position indicator elsewhere, writes still append at the end (the position indicator moves to end before each write). Reads can seek anywhere.

Compare the modes:

  • "w" — write, truncates to zero length.
  • "r" — read only.
  • "a" — append only (write-only).
  • "a+" — append + read.

The interview answer: read+append mode — content preserved, writes always land at the file’s end.

Answer:

Open for both reading and appending — existing content is preserved and all writes go to the end of the file.

"a+" (append + read/update) means:

  • The file is opened for reading and writing.
  • Existing content is preserved (unlike "w" which truncates).
  • The file is created if it doesn’t exist.
  • All write operations are forced to the end of the file — even if you fseek the position indicator elsewhere, writes still append at the end (the position indicator moves to end before each write). Reads can seek anywhere.

Compare the modes:

  • "w" — write, truncates to zero length.
  • "r" — read only.
  • "a" — append only (write-only).
  • "a+" — append + read.

The interview answer: read+append mode — content preserved, writes always land at the file’s end.

21. What is the purpose of va_start, va_arg, and va_end macros in <stdarg.h>?

Answer: They manage variadic (variable-argument) function parameter lists — functions that take a variable number of arguments, like printf.

A variadic function is declared with ...: int sum(int count, ...). To access the variable arguments:

#include <stdarg.h>
int sum(int count, ...) {
    va_list ap;
    va_start(ap, count);        // initialize after the last fixed param
    int total = 0;
    for (int i = 0; i < count; ++i)
        total += va_arg(ap, int);   // fetch next arg as int
    va_end(ap);                 // cleanup
    return total;
}
  • va_start(ap, last) — begins iteration, with the last named parameter.
  • va_arg(ap, type) — returns the next argument converted to type (types must be promoted: int for char/short args, double for float).
  • va_end(ap) — must be called before the function returns.

There’s no type safety — the callee must know the types (via the fixed args or format string), and printf-style format mismatch is UB. The interview answer: the <stdarg.h> macros for walking a variable argument list (va_start/va_arg/va_end).

Answer:

They manage variadic (variable-argument) function parameter lists — functions that take a variable number of arguments, like printf.

A variadic function is declared with ...: int sum(int count, ...). To access the variable arguments:

#include <stdarg.h>
int sum(int count, ...) {
    va_list ap;
    va_start(ap, count);        // initialize after the last fixed param
    int total = 0;
    for (int i = 0; i < count; ++i)
        total += va_arg(ap, int);   // fetch next arg as int
    va_end(ap);                 // cleanup
    return total;
}
  • va_start(ap, last) — begins iteration, with the last named parameter.
  • va_arg(ap, type) — returns the next argument converted to type (types must be promoted: int for char/short args, double for float).
  • va_end(ap) — must be called before the function returns.

There’s no type safety — the callee must know the types (via the fixed args or format string), and printf-style format mismatch is UB. The interview answer: the <stdarg.h> macros for walking a variable argument list (va_start/va_arg/va_end).

22. What happens if a recursive function in C lacks a valid base case?

Answer: Infinite recursion — each call pushes a new stack frame until the stack limit is exhausted, causing a stack overflow crash.

Every recursive call allocates a stack frame (return address, saved registers, locals). Without a terminating base case, the recursion never bottoms out, frames accumulate until the thread’s fixed stack memory is consumed, and the program faults (typically SIGSEGV/stack overflow).

void recurse() { recurse(); }   // no base case → stack overflow

Even with a base case, deeply nested recursion can still overflow on small stacks. The fix: ensure every recursive path reaches a base case, or convert to iteration for unbounded/very deep work. The interview answer: unbounded recursion exhausts the call stack and crashes with a stack overflow.

Answer:

Infinite recursion — each call pushes a new stack frame until the stack limit is exhausted, causing a stack overflow crash.

Every recursive call allocates a stack frame (return address, saved registers, locals). Without a terminating base case, the recursion never bottoms out, frames accumulate until the thread’s fixed stack memory is consumed, and the program faults (typically SIGSEGV/stack overflow).

void recurse() { recurse(); }   // no base case → stack overflow

Even with a base case, deeply nested recursion can still overflow on small stacks. The fix: ensure every recursive path reaches a base case, or convert to iteration for unbounded/very deep work. The interview answer: unbounded recursion exhausts the call stack and crashes with a stack overflow.

23. What is the result of bitwise operation 1 << 31 on a 32-bit signed int variable in C?

Answer: Undefined behavior — the shift moves a 1 into the sign-bit position, which overflows a signed int.

Left-shifting a signed value is well-defined only if the result is representable in the signed type. 1 << 31 on a 32-bit int produces 0x80000000 — a value whose bit pattern is the sign bit set, i.e., not representable as a positive int. Per the C standard that’s undefined behavior (pre-C23 rules).

The safe way to get the high bit is to use an unsigned type: 1u << 31 (or 1UL << 31) — well-defined, gives 0x80000000 = 2147483648. The interview answer: UB — shifting into the sign bit of a signed int; use an unsigned type instead.

Answer:

Undefined behavior — the shift moves a 1 into the sign-bit position, which overflows a signed int.

Left-shifting a signed value is well-defined only if the result is representable in the signed type. 1 << 31 on a 32-bit int produces 0x80000000 — a value whose bit pattern is the sign bit set, i.e., not representable as a positive int. Per the C standard that’s undefined behavior (pre-C23 rules).

The safe way to get the high bit is to use an unsigned type: 1u << 31 (or 1UL << 31) — well-defined, gives 0x80000000 = 2147483648. The interview answer: UB — shifting into the sign bit of a signed int; use an unsigned type instead.

24. What happens when calling fclose() on an open file stream in C?

Answer: It flushes buffered data to the file, closes the OS file descriptor, and releases the I/O buffers.

fclose(stream) performs the full teardown:

  1. Flushes any unwritten buffered output to the underlying file (like fflush).
  2. Closes the operating-system file descriptor.
  3. Frees the stream’s internal I/O buffer memory.

After fclose, the stream is invalid — using it further is undefined behavior. Note: the file itself is not deleted (that’s remove()); and failing to fclose can lose buffered output and leak file descriptors. The interview answer: flush + close descriptor + free stream buffers; the file remains on disk.

Answer:

It flushes buffered data to the file, closes the OS file descriptor, and releases the I/O buffers.

fclose(stream) performs the full teardown:

  1. Flushes any unwritten buffered output to the underlying file (like fflush).
  2. Closes the operating-system file descriptor.
  3. Frees the stream’s internal I/O buffer memory.

After fclose, the stream is invalid — using it further is undefined behavior. Note: the file itself is not deleted (that’s remove()); and failing to fclose can lose buffered output and leak file descriptors. The interview answer: flush + close descriptor + free stream buffers; the file remains on disk.

25. What does ptrdiff_t represent in <stddef.h>?

Answer: A signed integer type used to hold the result of subtracting two pointers into the same array.

ptrdiff_t is the result type of pointer subtraction:

int arr[10];
int *a = &arr[2], *b = &arr[7];
ptrdiff_t d = b - a;   // 5 — the element distance
  • It’s signed (can be negative).
  • It measures the distance in array elements, not bytes.
  • Defined in <stddef.h> (C) / <cstddef> (C++).
  • The subtraction is only well-defined when both pointers point into the same array (or one past the end).

It’s typically as wide as a pointer (e.g., 64-bit signed on 64-bit platforms). The interview answer: the signed result type of subtracting two pointers into the same array — the element distance.

Answer:

A signed integer type used to hold the result of subtracting two pointers into the same array.

ptrdiff_t is the result type of pointer subtraction:

int arr[10];
int *a = &arr[2], *b = &arr[7];
ptrdiff_t d = b - a;   // 5 — the element distance
  • It’s signed (can be negative).
  • It measures the distance in array elements, not bytes.
  • Defined in <stddef.h> (C) / <cstddef> (C++).
  • The subtraction is only well-defined when both pointers point into the same array (or one past the end).

It’s typically as wide as a pointer (e.g., 64-bit signed on 64-bit platforms). The interview answer: the signed result type of subtracting two pointers into the same array — the element distance.

26. What is the effect of applying logical OR || operator on boolean expressions in C?

Answer: Short-circuit evaluation — if the left operand is true (non-zero), the right operand is skipped.

|| evaluates left to right and stops as soon as the result is known:

  • If the left operand is non-zero (true), the whole expression is true regardless of the right side — so the right operand is not evaluated (no side effects from it).
  • Only if the left is 0 (false) is the right operand evaluated.
if (p != NULL && *p) { }       // && short-circuits too
if (flag || expensive()) { }   // expensive() skipped if flag true

This is the basis of safe idioms like ptr != NULL && ptr->field (the second operand only runs if ptr is valid). && is the mirror image: right side skipped when the left is false. The interview answer: || short-circuits — the right operand is skipped when the left is true.

Answer:

Short-circuit evaluation — if the left operand is true (non-zero), the right operand is skipped.

|| evaluates left to right and stops as soon as the result is known:

  • If the left operand is non-zero (true), the whole expression is true regardless of the right side — so the right operand is not evaluated (no side effects from it).
  • Only if the left is 0 (false) is the right operand evaluated.
if (p != NULL && *p) { }       // && short-circuits too
if (flag || expensive()) { }   // expensive() skipped if flag true

This is the basis of safe idioms like ptr != NULL && ptr->field (the second operand only runs if ptr is valid). && is the mirror image: right side skipped when the left is false. The interview answer: || short-circuits — the right operand is skipped when the left is true.

27. What is the output of integer division -7 / 3 in C99 and newer standards?

Answer: -2truncation toward zero.

-7 / 3 is -2.333.... C99 standardized integer division to truncate toward zero:

  • 7 / 3 = 2, -7 / 3 = -2 (the fractional part is discarded, moving toward zero, not toward negative infinity).

Pre-C99, the behavior was implementation-defined (some old compilers truncated toward negative infinity, giving -3). Since C99 it’s defined: toward zero. So -7 % 3 is -1 (the sign follows the dividend, and a = (a/b)*b + a%b holds). The interview answer: -2 — C99 truncates toward zero.

Answer:

-2truncation toward zero.

-7 / 3 is -2.333.... C99 standardized integer division to truncate toward zero:

  • 7 / 3 = 2, -7 / 3 = -2 (the fractional part is discarded, moving toward zero, not toward negative infinity).

Pre-C99, the behavior was implementation-defined (some old compilers truncated toward negative infinity, giving -3). Since C99 it’s defined: toward zero. So -7 % 3 is -1 (the sign follows the dividend, and a = (a/b)*b + a%b holds). The interview answer: -2 — C99 truncates toward zero.

My Private Notes

Notes are auto-saved locally to this device.