Skip to main content

Module Quiz

Complete this quiz after finishing all concept and practice pages.

Current Module Questions

Question 1: The Translation Pipeline

Name the four stages of C translation invoked by gcc hello.c -o hello. For each, name one kind of error that can occur only at that stage.

Answer: Preprocessor, compiler, assembler, linker.

Solution Walkthrough:

  1. Preprocessor: #include resolution, #define expansion, conditional compilation. A missing header file or unterminated #if fails here.
  2. Compiler: parses C, type-checks, emits assembly. A syntax error, type mismatch, or "used before declared" happens here.
  3. Assembler: turns assembly into machine code in a .o. Errors are rare unless inline assembly is wrong.
  4. Linker: resolves external symbols, assigns final addresses. "Undefined reference to foo" or "multiple definition of x" are linker errors.

Cross-check with gcc -E, gcc -S, gcc -c to observe each stage's output; see cppreference: Translation phases.

Question 2: Integer Promotion

Given unsigned char a = 200, b = 100;, what does printf("%d\n", a + b); print and why?

Answer: 300. Both a and b are promoted to int before the addition, so the result is 300 computed in int space, not 44 (which would be 300 % 256).

Question 3: Undefined Behavior vs Unsigned Wrap

Classify each as defined, implementation-defined, or undefined:

  • (a) unsigned u = UINT_MAX; u = u + 1;
  • (b) int i = INT_MAX; i = i + 1;
  • (c) signed char c = 127; c = c + 1;

Answer:

  • (a) Defined. Unsigned arithmetic wraps modulo 2^N, so u becomes 0.
  • (b) Undefined. Signed integer overflow is UB in C; the compiler may assume it does not happen.
  • (c) Undefined. c is promoted to int, 128 does not fit back into signed char, and the conversion back is implementation-defined or can raise a signal; most implementations wrap, but do not rely on it. K&R 2.7 and the C11 standard clause 6.3.1.3 cover the conversion.

Question 4: Sequence Points

Is int i = 0; int x = i++ + i++; defined in C11? Explain.

Answer: Undefined. Between the two side effects on i, there is no sequence point, and the C11 rule (6.5p2) says modifying the same object more than once between sequence points yields undefined behavior. Split into two statements.

Question 5: Array Decay

Inside void f(int a[10]), sizeof a yields what value, and why is it not 10 * sizeof(int)?

Answer: sizeof(int *). In a function parameter list, int a[10] is rewritten to int *a. The array decayed to a pointer before the function started, so the 10 has no runtime meaning and sizeof a is the size of a pointer on the target.

Question 6: Header vs Source

Which of the following belong in foo.h and which in foo.c? Justify each.

  • (a) int global_counter;
  • (b) extern int global_counter;
  • (c) static int helper(int x) { return x * 2; }
  • (d) int add(int a, int b);
  • (e) int add(int a, int b) { return a + b; }

Answer:

  • (a) foo.c: this is a definition; one per program.
  • (b) foo.h: declaration; callers need to see it.
  • (c) foo.c: static gives internal linkage; keep it private.
  • (d) foo.h: public prototype.
  • (e) foo.c: the single definition.

Question 7: strncpy Trap

Explain why char dst[5]; strncpy(dst, "hello", 5); puts(dst); is unsafe.

Answer: strncpy(dst, src, n) copies up to n bytes and pads with '\0' only if src is shorter than n. Here strlen("hello") == 5, so all 5 bytes are copied and no terminator is written. The subsequent puts(dst) walks past dst into memory it does not own: undefined behavior. Safe alternatives: snprintf(dst, sizeof dst, "%s", src) or a bounded strlcpy.

Question 8: gets Removal

Why was gets removed from C11, and what is the minimum safe replacement?

Answer: gets reads a line into a caller-supplied buffer with no size limit, which is an unfixable buffer-overrun vulnerability. It was deprecated in C99 and removed in C11. The minimum safe replacement is fgets(buf, sizeof buf, stdin), which bounds the read and always null-terminates; the caller must still check the return value for NULL at EOF/error and trim a trailing '\n'.

Question 9: Static at Block Scope

What is the difference between int counter(void) { int n = 0; return ++n; } and int counter(void) { static int n = 0; return ++n; }?

Answer: In the first, n has automatic storage duration: every call starts with n == 0, so the function always returns 1. In the second, n has static storage duration: it is initialized once before the program starts running and keeps its value between calls, so the function returns 1, 2, 3, ....

Question 10: Macro Hazards

Given #define MAX(a, b) ((a) > (b) ? (a) : (b)), why is MAX(i++, j++) still dangerous?

Answer: Because MAX is a text substitution, the selected expression is re-evaluated in the ternary. i++ and j++ are each expanded twice, causing either i or j to be incremented twice. Even full parentheses cannot fix double evaluation. Prefer static inline int imax(int a, int b);.

Question 11: Format String Mismatch

Classify and rank the severity:

  • (a) printf("%d\n", 3.14);
  • (b) printf("%ld\n", 5); where 5 is an int
  • (c) printf(user_input);

Answer: All three are undefined behavior.

  • (a) Passing a double where %d expects int corrupts what the function reads from the varargs area.
  • (b) Passing an int where %ld expects long is UB. On LP64 it often "works" because argument promotion masks the bug; on LLP64 it does not.
  • (c) Most dangerous in practice: a format specifier inside user_input reads addresses as arguments and can be used to read or write memory. Always use printf("%s", user_input);.

Question 12: Struct Padding

Why is sizeof(struct { char c; int i; }) often 8 rather than 5?

Answer: Alignment. int typically requires 4-byte alignment, so the compiler inserts 3 bytes of padding after c. The struct is then a multiple of its strictest alignment (4), giving total size 8. Reordering to struct { int i; char c; } still gives 8 because trailing padding keeps the struct a multiple of the alignment.

Question 13: Opaque Types

What does it mean for List to be an opaque type in a header, and what does it force callers to do?

Answer: The header declares typedef struct list List; but not the struct body. Callers can hold List * but cannot access members directly, take sizeof(List), or allocate one on the stack. They must use the module's accessor functions. This is the C idiom for information hiding.

Question 14: make Basics

Given a four-line Makefile with a target app: main.o util.o, explain why a make run after editing only util.c does not rebuild main.o.

Answer: make rebuilds a target when any prerequisite is newer than it. Editing util.c makes it newer than util.o, so util.o rebuilds; util.o then is newer than app, so app relinks. main.c is unchanged relative to main.o, so main.o is not touched.

Question 15: -Wall -Wextra -std=c11

Name at least three classes of bug that gcc -Wall -Wextra -std=c11 catches that a bare gcc does not.

Answer: Implicit function declarations and missing prototypes; format-string mismatches (%d with a long); uninitialized local variables (often via -Wmaybe-uninitialized); unused variables and parameters; comparison between signed and unsigned; switch cases missing a value; fall-through warnings; many more. -std=c11 also enforces rules like the removal of gets.

Interleaved Review Questions

Prior Module Question 1 (S2 Module 1 - Algorithm Analysis)

Given T(n) = 2T(n/2) + n, classify the asymptotic growth of T(n) and say which design paradigm commonly produces this recurrence.

Answer: Theta(n log n) by the Master Theorem (case 2 with a = 2, b = 2, f(n) = n = Theta(n^log_2 2) = Theta(n)). Divide-and-conquer algorithms like mergesort produce this recurrence.

Prior Module Question 2 (S3 Design - Design Principles)

What is the single-responsibility principle, and why does it also apply to a C module with a .h/.c pair?

Answer: A module should have exactly one reason to change. In C it means each .c file should implement one coherent responsibility, and its .h should expose only the interface that responsibility requires. Mixing unrelated responsibilities in one translation unit makes rebuilds broader and the interface harder to evolve.

Prior Module Question 3 (S1 Module 2 - Counting and Proof)

Why is a proof by examples not enough for a universal claim?

Answer: Because checking finitely many instances leaves infinitely many uncovered. Universal claims require a proof by structural argument (induction, exchange, counting, etc.), not by enumeration.

Prior Module Question 4 (S1 Module 3 - Probability)

What does the law of large numbers say about sample averages, and why is it relevant for stress-testing a C implementation against a random input generator?

Answer: For i.i.d. samples, the sample average converges to the true mean as n grows. For stress testing, it means that if your reference and test agree on the distribution of random inputs in large batches, occasional disagreements are signal, not noise.

Prior Module Question 5 (S2 Module 1 - Correctness)

State a loop invariant for for (size_t i = 0; i < n; i++) s += a[i];.

Answer: At the top of iteration i, s equals the sum of a[0..i-1]. At termination (i == n), s equals the sum of all elements, which is the intended postcondition.

Self-Assessment and Remediation

Mastery Level (90-100% correct):

  • Advance with confidence; keep spaced repetition on translation pipeline and array decay.

Proficient Level (75-89% correct):

  • Review the concept page behind each missed answer and redo two drills from the matching practice page.

Developing Level (60-74% correct):

  • Rework Practice 1 and Practice 2 end-to-end. Redo Kata 3 and Kata 6.
  • If most misses are in strings or I/O, focus on Practice 3.

Insufficient Level (<60% correct):

  • Return to the concept sequence Clusters 1-3 and rebuild the mental model before touching Clusters 4-5 again.
  • Rerun gcc -E, gcc -S, gcc -c, nm, and make by hand on your own tiny project until the pipeline is second nature.