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 ajmp_buf, and returns0the first time.longjmp(buf, value)restores that saved environment and jumps execution back to thesetjmppoint — 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 ajmp_buf, and returns0the first time.longjmp(buf, value)restores that saved environment and jumps execution back to thesetjmppoint — 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
printfwithfprintf(stderr, ...)(stderr is unbuffered, so ordering matters) — flushstdoutso 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
printfwithfprintf(stderr, ...)(stderr is unbuffered, so ordering matters) — flushstdoutso 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
SIGABRTsignal; 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
SIGABRTsignal; 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 whenmainreturns (normal termination only — not onabort()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 whenmainreturns (normal termination only — not onabort()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: 10 — 012 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: 10 — 012 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.
memcpyis 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).memmoveguarantees 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.
memcpyis 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).memmoveguarantees 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-1if 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 orexec-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-1if 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 orexec-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>0for 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>0for 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) == 1always.CHAR_BITgives 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) == 1always.CHAR_BITgives 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
fseekthe 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
fseekthe 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 totype(types must be promoted:intforchar/shortargs,doubleforfloat).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 totype(types must be promoted:intforchar/shortargs,doubleforfloat).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:
- Flushes any unwritten buffered output to the underlying file (like
fflush). - Closes the operating-system file descriptor.
- 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:
- Flushes any unwritten buffered output to the underlying file (like
fflush). - Closes the operating-system file descriptor.
- 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: -2 — truncation 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:
-2 — truncation 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.
Premium Content
Unlock Operators, Stdlib & I/O and all premium lessons with a subscription.
From ₹199.99/year — See plans