16. Modern C and the C interview playbook¶
Time: ~3 hours (plus practice: write every coding answer twice on paper) Prerequisites: modules 01 to 15
1. In plain words¶
A C interview tests three things. First, precise knowledge of the language: what static
means in each place, why volatile is not for threads, what sizeof gives for an array
parameter. Second, writing small correct functions without a library: strlen, memmove,
atoi, reversing a list. Third, care: do you mention the empty string, the NULL pointer, the
overflow, the overlap, before the interviewer has to ask?
Think of a driving test. Everyone can drive in a straight line. The examiner is watching whether you check the mirrors at every turn. In a C interview the mirrors are the edge cases and the undefined behaviour, and saying them out loud is how the interviewer knows you checked.
This module is the reference sheet for that day: what each C standard added (including C23), the C
versus C++ differences interviewers like, the classic conceptual questions with model answers, the
classic coding questions with complete tested solutions in code/, and how C rounds are run at
embedded, systems and product companies.
2. The real thing¶
2.1 The C standards in one table¶
| Standard | Year | What it added that you are expected to know |
|---|---|---|
| K&R C | 1978 | the original language of the book |
| C89 / C90 | 1989 / 1990 | ANSI then ISO standard: prototypes, void *, const, volatile, the standard library |
| C99 | 1999 | // comments; declarations anywhere and in for; long long; <stdint.h>, <inttypes.h>, <stdbool.h>; inline; restrict; variable length arrays; designated initialisers; compound literals; flexible array members; snprintf; __func__; variadic macros; static in array parameters; integer division defined to truncate toward zero |
| C11 | 2011 | _Static_assert; _Generic; _Alignas / _Alignof; _Noreturn; _Thread_local; <threads.h>; <stdatomic.h> and a memory model; anonymous structs and unions; aligned_alloc; quick_exit; gets removed; VLAs made optional (__STDC_NO_VLA__); optional Annex K (_s functions) |
| C17 (also called C18) | 2018 | defect fixes only, no new features; __STDC_VERSION__ is 201710L |
| C23 | ISO/IEC 9899:2024 | bool, true, false, nullptr, static_assert, alignas, thread_local as keywords; constexpr objects; typeof; auto type inference; [[nodiscard]], [[maybe_unused]], [[fallthrough]], [[deprecated]]; digit separators 1'000'000; binary literals 0b1010; = {} empty initialiser; #embed; #elifdef; <stdckdint.h> (ckd_add, ckd_mul); memset_explicit; strdup standardised; _BitInt(N); two's complement required; void f() now means "no parameters"; old K&R function definitions removed |
GCC 13 implements most of C23 under -std=c2x. This snippet uses several C23 features; it
compiles cleanly with gcc -std=c2x -Wall -Wextra -Wpedantic on GCC 13.3 and prints
1000 10 1 1 3.5 1:
#include <stdio.h>
[[nodiscard]] static int f(void) { return 1; }
int main(void) {
constexpr int N = 1'000; /* constexpr object, digit separator */
typeof(N) m = 0b1010; /* typeof, binary literal */
int *p = nullptr; /* nullptr */
bool ok = true; /* bool is a keyword, no header */
auto x = 3.5; /* type inferred: double */
static_assert(sizeof(int) == 4); /* message now optional */
int arr[4] = {}; /* empty initialiser */
printf("%d %d %d %d %g %d\n", N, m, p == nullptr, ok, x, arr[0] + f());
return 0;
}
GCC 13 does not have <stdckdint.h> (it arrived in GCC 14; __builtin_add_overflow is the
GCC 13 equivalent) or #embed (GCC 15). Most production C today is C99 or C11; knowing what C23
changes is a plus, assuming it in an interview answer is not.
2.2 C versus C++: the differences interviewers ask about¶
| Topic | C | C++ |
|---|---|---|
void * conversion |
implicit: int *p = malloc(n); is correct and idiomatic |
needs a cast; g++ says error: invalid conversion from 'void*' to 'int*' [-fpermissive] |
| Struct tags | struct point p; unless you typedef it |
point p; works: the tag is a type name |
const int N = 10; |
a read-only object, not a constant expression: at file scope int a[N]; is an error, at block scope it makes a VLA |
a constant expression: int a[N]; is a normal array |
const at file scope |
external linkage | internal linkage |
bool |
_Bool plus <stdbool.h> macros (keywords since C23) |
keyword |
sizeof('a') |
sizeof(int): a character literal is an int |
1: it is a char |
| VLAs | C99; optional since C11 | not in the standard (a GCC extension) |
| Designated initialisers | any order, arrays ([3] = 7), nested |
C++20, declaration order only, no array designators |
| Compound literals | yes | no (extension) |
void f() |
before C23: unspecified parameters; C23: no parameters | no parameters |
restrict |
yes | no (compilers offer __restrict) |
| Union type punning | defined: the bytes are reinterpreted | undefined behaviour; use memcpy or std::bit_cast |
int x; int x; at file scope |
legal (tentative definitions) | error: redefinition |
goto over an initialisation |
allowed (except into a VLA's scope) | error |
inline |
C99 rules: an inline definition without one extern declaration in some .c file gives undefined reference at -O0 |
ODR rules: just works |
| Names in object files | kv_close |
mangled: _Z8kv_closeP2kv |
Name mangling and extern "C". C++ encodes parameter types into symbol names so that
overloads can coexist. Real nm output for the same function compiled as C++, with and without
extern "C":
0000000000000000 T _Z8kv_closeP2kv <- int kv_close(struct kv *) (C++ linkage)
0000000000000019 T kv_close_c <- extern "C" int kv_close_c(struct kv *)
_Z starts a mangled name, 8kv_close is the name with its length, P2kv is "pointer to the type
named kv". A C library header used from C++ must therefore wrap its declarations, or the C++
caller looks for _Z8kv_closeP2kv and the link fails with undefined reference:
#ifdef __cplusplus
extern "C" {
#endif
int kv_open(kv_t **out, size_t capacity);
void kv_close(kv_t *kv);
#ifdef __cplusplus
}
#endif
The C99 inline trap, real GCC 13 output for inline int twice(int x) { return 2 * x; }
called from main in one file at -O0:
At -O2 the call is inlined and it links, so the bug hides in release builds. In C, a plain
inline definition is only an inline definition; one translation unit must also provide the
external definition (extern inline int twice(int);). The everyday fix: static inline in headers.
2.3 Classic conceptual questions¶
static in three contexts.
- Inside a function: the variable has static storage duration: it is initialised once,
before
main, and keeps its value between calls (not thread-safe without a lock or atomic). - At file scope, on a function or variable: internal linkage: the name is invisible to other
.cfiles, so two files can each have a privatehelper()without a link clash. - In an array parameter (C99):
void f(int a[static 4])promises the caller passes at least 4 valid elements, so the compiler may optimise and can warn on violations.
const int *p, int *const p, const int *const p. Read right to left from the name:
p is a pointer to a const int (you cannot write *p, you can change p); p is a const pointer
to an int (you can write *p, not p); both const. int const *p means the same as
const int *p. A const on the pointer itself is top-level and is dropped when the value is
copied, which is why _Generic sees int *const cpi as int * (m16_modern_c_features.c checks it).
volatile, and why it is not for threads. volatile tells the compiler that every read and
write of the object is a visible side effect: it must not cache the value in a register, merge or
remove accesses. That is what memory-mapped hardware registers need, and what a
volatile sig_atomic_t flag set by a signal handler needs (the only object type a handler may
portably write). It does not make a read-modify-write atomic, it does not order other memory
accesses around it, and a concurrent non-atomic access from two threads is still a data race,
which is undefined behaviour. Use _Atomic or a mutex (module 14). (MSVC historically gave
volatile acquire/release semantics on x86 as an extension, /volatile:ms; code relying on it
breaks on other compilers and on ARM.)
sizeof of an array parameter. An array parameter is adjusted to a pointer, so
void f(int a[10]) is void f(int *a) and sizeof a is 8 on a 64-bit system. GCC warns:
warning: 'sizeof' on array function parameter 'a' will return size of 'int *' [-Wsizeof-array-argument]
Pass the length separately (f(int *a, size_t n)), or pass a pointer to the array
(void f(int (*a)[10]), where sizeof *a is 40).
Array versus pointer. char a[] = "hi"; is a 3-byte array you may modify; sizeof a is 3.
char *p = "hi"; is a pointer to a string literal, which you must not modify (it is usually in
read-only memory); sizeof p is 8. An array name decays to a pointer to its first element in most
expressions, except as the operand of sizeof, &, or as a string literal initialising an array.
More you should answer in two sentences each: i++ + ++i (unsequenced modifications: UB);
-1 < 1u is false (the -1 is converted to unsigned int, a huge value); unsigned char
arithmetic is done in int (integer promotion); malloc versus calloc (zeroed, and checks the
n * size overflow) versus realloc (may move; keep the old pointer until it succeeds);
free(NULL) is a no-op; macros versus static inline functions (MAX(a++, b) evaluates a++
twice); why do { ... } while (0) in macros (one statement, safe after if without braces);
header guards versus #pragma once; extern declarations versus definitions; what runs before
main (section 4).
2.4 Classic coding questions: approach and edge cases¶
All of these are implemented and tested in code/m16_string_questions.c and
code/m16_classic_questions.c. For each, say the edge cases before you write code.
| Question | Approach | Edge cases to say out loud |
|---|---|---|
strlen |
walk to the NUL, return the pointer difference | empty string; NULL is the caller's bug (the standard function does not check) |
strcpy |
copy including the NUL; return dst |
overlap is UB; caller must size dst; prefer a bounded copy |
strncpy trap |
does not NUL-terminate when the source is too long, and pads with zeros | why strlcpy (or snprintf(dst, n, "%s", src)) is the safe answer |
strcmp |
compare as unsigned char until a difference or NUL |
bytes above 127; return sign only |
memcpy |
byte loop; restrict says no overlap |
overlap is UB; n == 0 |
memmove |
copy forwards if dst < src, backwards if dst > src |
both overlap directions; equal pointers; comparing unrelated pointers |
atoi with overflow |
skip spaces, sign, digits; check before acc * 10 + d |
INT_MIN (magnitude one more than INT_MAX), "+", "-", "", trailing junk |
| Reverse a string in place | two indices moving inward | empty, one character, even and odd lengths |
| Reverse a linked list | three pointers: prev, cur, next | empty list, one node; recursive version uses O(n) stack |
| Detect a loop | Floyd: slow moves 1, fast moves 2; they meet inside the loop | loop at the head, self-loop, no loop |
| Find where the loop starts | after meeting, move one pointer to the head; step both by 1 | whole list is the loop |
| Endianness | look at the first byte of uint32_t 1 through unsigned char * or a union |
say "union punning is legal in C, not in C++" |
| Count set bits | v &= v - 1 clears the lowest set bit; loop count = bits |
0, all ones; __builtin_popcount |
| Power of two | x != 0 && (x & (x - 1)) == 0 |
zero is not a power of two |
| Stack | array + length + capacity, grow by doubling | pop on empty returns an error, never UB |
| Tokenizer | strspn to skip delimiters, strcspn to find the end |
empty fields (a CSV needs them; strtok drops them), state in the caller |
| Rotate an array right by k | reverse all, reverse first k, reverse the rest | k >= n (use k % n), n == 0 |
Four of them in full, because they are asked most:
/* memmove: overlap allowed. If dst is after src, copy backwards so we never read a byte we
* already overwrote. Comparing pointers into DIFFERENT objects with < is undefined in C, so
* compare their integer values (implementation-defined, correct on flat-memory platforms). */
void *my_memmove(void *dst, const void *src, size_t n)
{
unsigned char *d = dst;
const unsigned char *s = src;
if ((uintptr_t)d < (uintptr_t)s) {
while (n--)
*d++ = *s++;
} else if ((uintptr_t)d > (uintptr_t)s) {
d += n;
s += n;
while (n--)
*--d = *--s;
}
return dst;
}
/* atoi with overflow detection: accumulate in unsigned so INT_MIN's magnitude fits. */
int my_atoi(const char *s, int *out, const char **end)
{
while (isspace((unsigned char)*s)) /* cast: isspace(negative char) is UB */
s++;
int neg = 0;
if (*s == '+' || *s == '-')
neg = (*s++ == '-');
if (!isdigit((unsigned char)*s)) {
if (end) *end = s;
return ATOI_NO_DIGITS;
}
unsigned limit = neg ? (unsigned)INT_MAX + 1u : (unsigned)INT_MAX;
unsigned acc = 0;
for (; isdigit((unsigned char)*s); s++) {
unsigned d = (unsigned)(*s - '0');
if (acc > (limit - d) / 10) { /* acc * 10 + d would exceed limit */
*out = neg ? INT_MIN : INT_MAX; /* clamp, like strtol */
while (isdigit((unsigned char)*s))
s++;
if (end) *end = s;
return ATOI_OVERFLOW;
}
acc = acc * 10 + d;
}
if (neg)
*out = acc == (unsigned)INT_MAX + 1u ? INT_MIN : -(int)acc;
else
*out = (int)acc;
if (end) *end = s;
return ATOI_OK;
}
/* Reverse a singly linked list: each step turns one arrow around. O(n) time, O(1) space. */
static struct node *reverse_iter(struct node *head)
{
struct node *prev = NULL;
while (head) {
struct node *next = head->next;
head->next = prev;
prev = head;
head = next;
}
return prev;
}
/* Floyd's cycle detection, plus the start of the loop. */
static const struct node *loop_start(const struct node *head)
{
const struct node *slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
for (slow = head; slow != fast; slow = slow->next, fast = fast->next)
;
return slow;
}
}
return NULL;
}
Why the loop start works: let the distance from the head to the loop start be a, and the
meeting point be b steps into a loop of length L. Fast travelled twice as far as slow:
2(a + b) = a + b + kL, so a = kL - b. Walking a steps from the head and a steps from the
meeting point therefore lands both pointers on the loop start.
The overflow check in my_atoi deserves one sentence in the interview: "I check before
multiplying, because once acc * 10 + d has overflowed it is too late to detect it", which links
straight back to module 09.
2.5 How C interview rounds run¶
The format varies by company type more than by country. Typical patterns:
- Embedded and firmware (automotive, semiconductors, devices). A written or online test with
"what does this print" snippets (operator precedence, integer promotion,
sizeof, pointer arithmetic,static), bit manipulation (set, clear, toggle a bit; count bits; swap nibbles),volatileand interrupt service routines (keep them short, nomalloc, noprintf), memory-mapped registers, struct packing and alignment, endianness, fixed-point arithmetic, and RTOS questions (mutex versus semaphore, priority inversion). Coding without dynamic memory is common. - Systems (operating systems, networking, storage, databases, infrastructure). Pointers and
memory layout, writing an allocator or a hash table in C, concurrency (a bounded queue, a race to
spot), system calls (
fork/exec,read/writeloops, sockets,mmap), and a debugging discussion: "this server crashes once a day; here is the stack; what do you do?" Your RCA and crash-dump experience is the strongest card here (05_Memory_Debugging_RCA, 11_Interview_Prep_Job_Search/06_debugging_and_rca_round.md). - Product companies hiring for C, C++ backend roles. One or two data-structures rounds where
you usually may choose C++ (use it: the STL saves time; 03_Data_Structures_Algorithms/16_interview_coding_playbook.md),
a language round that mixes C and C++ (manual memory versus RAII,
extern "C", undefined behaviour), and a design round (11_Interview_Prep_Job_Search/01_how_cpp_systems_interviews_work.md).
How to answer a C coding question, every time:
- Repeat the problem and ask about the contract: can the input be NULL? Can buffers overlap? What should happen on overflow: clamp, error, or is it the caller's problem?
- List the edge cases out loud (table in 2.4).
- Write the code, small and plain: no clever tricks you cannot explain.
- Walk through it by hand with one normal case and two edge cases.
- State complexity, and name any undefined behaviour you avoided ("I compare as
unsigned char", "I check before multiplying").
3. Code¶
3.1 m16_string_questions.c¶
my_strlen, my_strcmp, my_strcpy, my_strlcpy, my_memcpy, my_memmove, my_atoi,
reverse_in_place, my_strtok_r and a non-destructive next_field, each tested against the C
library or a table. The tests include both overlap directions for memmove, every atoi boundary
("2147483647", "2147483648", "-2147483648", "-2147483649", "-", "", " -17xyz"),
and a CSV line where strtok loses the empty fields that next_field keeps:
const char *csv = "TXN,,ACC-1001,";
const char *cur = csv, *f;
size_t flen, nf = 0;
size_t lens[] = { 3, 0, 8, 0 };
while (next_field(&cur, ',', &f, &flen))
assert(nf < 4 && flen == lens[nf++]);
assert(nf == 4);
It also runs clean under ASan and UBSan:
gcc -std=c17 -Wall -Wextra -Wpedantic -Werror -O1 -g -fsanitize=address,undefined -o m16_strings_asan m16_string_questions.c.
3.2 m16_classic_questions.c¶
Lists (iterative and recursive reverse, Floyd, loop start, middle), bits (endianness, byte swap,
three popcounts cross-checked on 100 000 values, power of two), a growable stack with a
balanced-brackets checker, rotation checked against a naive version for every n up to 7 and every
k up to 2n + 1, the XOR swap and its aliasing trap, and sizeof through a pointer to an array.
Real output:
The XOR swap is in the file to show why it is a bad answer: xor_swap(&a, &a) sets a to 0
unless you guard against equal pointers, and it is slower than a temporary on modern CPUs.
3.3 m16_modern_c_features.c¶
C99 and C11 features with checks: designated initialisers (struct and array), compound literals,
a flexible array member, _Static_assert (including sizeof('a') == sizeof(int)), _Generic,
bool conversion, PRId64, alignas, VLAs, integer division, static in all three contexts,
const pointer forms, volatile sig_atomic_t set by a real signal handler, and _Noreturn.
Real output:
#define TYPE_NAME(x) _Generic((x), \
int: "int", unsigned: "unsigned", long: "long", double: "double", \
char *: "char *", const char *: "const char *", int *: "int *", \
const int *: "const int *", default: "other")
char c1 = 'a', c2 = 'b';
assert(strcmp(TYPE_NAME(c1 + c2), "int") == 0); /* integer promotion, made visible */
4. Under the hood (senior-level)¶
What happens before and after main. The kernel starts the program at _start (from
crt1.o), which calls glibc's __libc_start_main. That sets up the C library (TLS, stdio,
atexit machinery), runs the functions in .init_array (constructors: GCC
__attribute__((constructor)), and C++ static initialisers), then calls main(argc, argv, envp).
When main returns, its value goes to exit, which runs atexit handlers and .fini_array
destructors in reverse order, flushes and closes stdio streams, and calls _exit. A crash, abort
or _exit skips all of that, which is why buffered output is lost (module 13).
How glibc's memcpy and strlen are really written. They are not the byte loops above. glibc
selects an implementation at load time through an IFUNC resolver based on the CPU (SSE2, AVX2,
AVX-512, rep movsb with ERMS). Small copies use overlapping unaligned loads and stores instead of
loops. strlen reads a whole word or vector at a time and detects a zero byte with a bit trick: for
32-bit words, (x - 0x01010101) & ~x & 0x80808080 is non-zero exactly when some byte of x is
zero. Reading a full aligned word may read past the terminator, which would be undefined in C but is
safe in hand-written assembly because an aligned word never crosses a page boundary. Mentioning
that in an interview, after writing the simple loop, is a good senior signal.
The 2010 memcpy story. In 2010 glibc changed its x86-64 memcpy so that, on some CPUs, it
copied backwards. Programs that had (incorrectly) used memcpy on overlapping buffers started
producing corrupt output; Adobe's Flash plugin on Fedora was the famous case. The programs had
always had undefined behaviour; a faster library exposed it. It is the clearest real example of
why "memcpy for non-overlapping, memmove for overlapping" is a rule, not a style preference.
popcount in hardware. With -mpopcnt or -march=native, __builtin_popcount becomes one
POPCNT instruction; without it GCC emits a short bit-twiddling sequence or a library call. Many
compilers also recognise the Kernighan loop and replace it with POPCNT. Interviewers like hearing
both the loop and the builtin.
Reading C++ mangled names. c++filt _Z8kv_closeP2kv prints kv_close(kv*). When a mixed C and
C++ build fails with undefined reference to 'kv_close(kv*)', the demangled signature in the error
tells you at once that a C function was declared without extern "C".
5. In production¶
strncpyin old code. It was designed for fixed-width fields in old Unix directory entries, not as a "safestrcpy". Production code that uses it to copy into a buffer and then prints the buffer has a missing-terminator bug waiting for a long enough input. When you seestrncpy(dst, src, sizeof dst)withoutdst[sizeof dst - 1] = '\0', you have found a bug.atoion input.atoi("12abc")is 12 andatoi("abc")is 0, with no error. In a banking message parser that turns a corrupted amount into a valid one. Usestrtolwith the checks from module 10 or a parser likemy_atoithat reports errors.- Mixed C and C++ builds. When a C++ service links a C library (Berkeley DB, libpq, OpenSSL),
missing
extern "C"produces link errors, and mismatched struct definitions between the C and C++ sides produce memory corruption. Include the library's own header; never re-declare its structs. - Interview answers become code review comments. Every edge case in the table in 2.4 is a real
review comment you will write on a colleague's patch: "this compares
charas signed", "this memcpy overlaps", "this atoi accepts garbage". Practising the questions is practising the job.
6. Try it¶
- Easy - five from memory. On paper, with no references, write
strlen,strcmp,memmove,reverse_iterandis_power_of_two. Type them in, add them to a copy of the test files, and run. Done when all tests pass on the first compile, or you know exactly which edge case you missed. - Easy - what does this print? Predict, then run:
printf("%zu %zu\n", sizeof("abc"), strlen("abc"));,printf("%d\n", -1 < 1u);,unsigned char c = 200; printf("%d\n", c + c);,int a[5]; printf("%td\n", &a[4] - &a[0]);. Done when every prediction matches and you can give the rule behind each. - Medium - strtod-free parser. Write
parse_amount("1234.56", &paise)that accepts an optional sign, digits, and an optional point with exactly two digits, and rejects everything else (including overflow oflong long). Done when a table test of 20 inputs passes under UBSan. - Medium - C23 tour. Rewrite
m16_modern_c_features.cfor-std=c2x:boolwithout the header,nullptr,constexpr,typeof,[[nodiscard]]on a function whose result you then ignore (read the warning),static_assertwithout a message. Done when it builds with GCC 13-std=c2x -Wall -Wextra -Wpedanticand you have listed which C23 features GCC 13 lacks. - Hard - word-at-a-time
strlen. Implementstrlenthat first walks byte by byte to an 8-byte boundary, then readsuint64_twords withmemcpyand uses the zero-byte bit trick. Done when it matchesstrlenon every length from 0 to 100 at every starting alignment 0 to 7, and you can explain why the C version must not read past the buffer while glibc's assembly may. - Hard - mock interview. Have someone pick five questions from section 2.3 and three coding questions from 2.4, 45 minutes in total, coding in a plain editor without compiling. Done when you have done it twice and written down every edge case you forgot to say out loud.
7. Interview questions¶
Q: What does static mean in C?
A: Inside a function it gives a local variable static storage duration: initialised once and kept
between calls. At file scope it gives a function or variable internal linkage, hiding it from other
translation units. In an array parameter, int a[static 4] promises that the argument points to at
least four elements.
Q: Why is volatile not suitable for sharing data between threads?
A: It only stops the compiler from caching or removing accesses; it provides no atomicity for
read-modify-write operations and no ordering relative to other memory, and a concurrent
non-atomic access is still a data race, which is undefined. Use C11 atomics or a mutex. volatile
is for memory-mapped I/O and volatile sig_atomic_t signal-handler flags.
Q: What is the difference between const int *p and int *const p?
A: In const int *p the pointed-to int is read-only through p but p can be re-pointed. In
int *const p the pointer itself is fixed but the int can be modified through it. const int *const p
fixes both. Reading the declaration from the name outward, right to left, gives the meaning.
Q: What does sizeof return for an array parameter, and why?
A: The size of a pointer, because a parameter declared as an array is adjusted to a pointer to its
element type; the array's length is not passed. GCC warns with -Wsizeof-array-argument. Pass the
length as a separate argument or pass a pointer to an array of known size.
Q: Write memmove. How is it different from memcpy?
A: memmove must work when source and destination overlap, so it copies forwards when the
destination is before the source and backwards when it is after, so it never reads a byte it has
already overwritten. memcpy may assume no overlap (its parameters are restrict), which allows
faster copying; using it on overlapping buffers is undefined behaviour.
Q: How do you detect overflow in atoi?
A: Accumulate the magnitude in an unsigned type and, before each acc * 10 + digit, check
acc > (limit - digit) / 10, where the limit is INT_MAX for positive numbers and INT_MAX + 1
for negative ones so that INT_MIN parses. Checking after the multiplication is too late, since
signed overflow is undefined. Report where parsing stopped so trailing junk can be rejected.
Q: Why does C++ need extern "C" to call C functions?
A: C++ mangles function names to encode parameter types, so int kv_close(struct kv *) becomes
_Z8kv_closeP2kv, while the C library exports plain kv_close. extern "C" tells the C++
compiler to use C linkage and the unmangled name. C headers wrap their declarations in
#ifdef __cplusplus extern "C" { ... }.
Q: Name three differences between C and C++ that can change the meaning of the same code.
A: sizeof('a') is sizeof(int) in C and 1 in C++; reading a different union member than the one
last written is defined in C and undefined in C++; and void f() declares a function with
unspecified parameters in C before C23 but no parameters in C++. Also, const int n = 10; is a
constant expression in C++ but not in C, where int a[n] inside a function creates a VLA.
8. Recap¶
- C99 gave most of modern C (stdint, bool, inline, restrict, VLAs, designated initialisers,
compound literals, flexible arrays); C11 added
_Generic,_Static_assert, alignment, threads and atomics; C17 fixed defects; C23 addsnullptr,constexpr,typeof, attributes,ckd_add,#embedand makesboola keyword. - GCC 13 supports most of C23 with
-std=c2x, but not<stdckdint.h>(GCC 14) or#embed(GCC 15). - C versus C++: implicit
void *conversion, struct tags,constand constant expressions,sizeof('a'), union punning,void f(), VLAs, designated initialisers, C99inline. - C++ mangles names (
_Z8kv_closeP2kv); C headers needextern "C"guards. static: storage duration inside a function, internal linkage at file scope, minimum size in array parameters.volatileis for hardware registers and signal flags, never for thread synchronisation.- An array parameter is a pointer;
sizeofgives 8; pass the length. - Coding answers: state the contract and edge cases first, write plain code, trace two edge cases by hand, state complexity and the UB you avoided.
memmovecopies backwards when the destination is after the source;atoichecks before multiplying and parsesINT_MIN; Floyd finds loops and their start.- Embedded rounds stress bits,
volatileand no-malloc code; systems rounds stress memory, concurrency, syscalls and debugging; product rounds mix DSA (use C++) with C and C++ language questions.