Project 1: C Declaration Parser (cdecl Clone)

Build a command-line tool that parses complex C declarations and translates them to English using the clockwise/spiral rule.


Quick Reference

Attribute Value
Language C
Difficulty Level 3 (Intermediate)
Time Weekend (8-16 hours)
Book Reference Expert C Programming, Chapter 3
Coolness Interview Gold - Classic systems question
Portfolio Value High - Demonstrates parsing skills

Learning Objectives

By completing this project, you will:

  1. Master C declaration syntax - Read any declaration without hesitation, from simple to arcane
  2. Implement the clockwise/spiral rule - Transform the mental algorithm into working code
  3. Understand operator precedence in declarations - Know why int *p[10] differs from int (*p)[10]
  4. Parse context-free grammar manually - Build a recursive descent parser for C declarations
  5. Distinguish declarators from specifiers - Separate type information from identifier binding
  6. Handle type qualifiers correctly - Understand where const binds in different positions
  7. Build a lexer-parser system - Create tokens from text and build structure from tokens
  8. Generate human-readable output - Translate abstract syntax into clear English

The Core Question You’re Answering

“Why does C declaration syntax read inside-out and right-to-left, and how can we systematically decode any declaration?”

C declarations are notoriously confusing because they were designed to mimic usage. The declaration int *p says that *p has type int, meaning p is a pointer to int. This “declaration follows use” principle, combined with operator precedence, creates the seemingly bizarre syntax where you must read declarations in a spiral pattern from the identifier outward.

Understanding this deeply means you can:

  • Read any declaration in legacy codebases
  • Debug function pointer callback issues
  • Write complex type definitions correctly
  • Ace systems programming interviews

Theoretical Foundation

Why C Declarations Are Hard

Dennis Ritchie designed C declarations so that the declaration of a variable mimics its use. This principle, while elegant in theory, leads to confusing syntax:

Declaration Follows Use:

    int x;          // x is an int
    int *p;         // *p is an int, so p is a pointer to int
    int a[10];      // a[i] is an int, so a is an array of int
    int f();        // f() is an int, so f is a function returning int

The problem comes when these combine:

Complex Declaration Analysis:

    char *(*fp)(int, float);

    Reading this requires understanding:
    1. Identifier is 'fp'
    2. ( ) groups 'fp' with the first '*'
    3. So fp is a pointer to... something
    4. (int, float) means that something is a function
    5. Returning char * (pointer to char)

    Result: "fp is a pointer to a function taking (int, float) returning pointer to char"

The Clockwise/Spiral Rule

The clockwise/spiral rule, documented in Expert C Programming Chapter 3, provides a systematic way to read any C declaration:

THE CLOCKWISE/SPIRAL RULE ALGORITHM:
=====================================

Step 1: Find the identifier (the variable/function name)

Step 2: Start reading "spiral clockwise":
        - Go right: read array bounds [ ] or function parameters ( )
        - Go left:  read pointer symbols * and type qualifiers
        - Repeat until you've consumed all elements

Step 3: Parentheses ( ) act as grouping - they redirect the spiral

VISUAL EXAMPLE: char *(*fp)(int, float)
=====================================================

                    ┌─────────────────────────────┐
                    │       ┌──────────────┐      │
                    │       │   ┌─────┐    │      │
                    │       │   │     │    │      │
                    ▼       ▼   ▼     │    │      │
                  char   *  (  *  fp  )  (int, float)
                    ▲       ▲      │   ▲
                    │       │      │   │
                    │       │      └───┘
                    │       └──────────────────────
                    └───────────────────────────────

Reading order:
1. Start at 'fp'
2. Go right: hit ')', so bounce back
3. Go left: '*' → "pointer to"
4. Go right past ')': '(int, float)' → "function taking int and float"
5. Go left: '*' → "returning pointer to"
6. Go left: 'char' → "char"

Result: "fp is a pointer to a function(int, float) returning pointer to char"

Declaration Syntax Components

A C declaration has two main parts:

DECLARATION STRUCTURE:
======================

┌────────────────────────────────────────────────────┐
│              static const unsigned long int        │  Declaration Specifiers
│              ───┬── ──┬── ───┬──── ──┬─ ─┬─       │  (storage class, qualifiers, type)
│                 │     │      │       │   │         │
│           storage  type   sign   size type        │
│           class  qualifier modifier modifier      │
└────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────┐
│                  *(*fp)[10]                        │  Declarator
│                  ─┬───┬───┬─                       │  (how to derive the type)
│                   │   │   │                        │
│              pointer identifier array             │
│              modifier           modifier          │
└────────────────────────────────────────────────────┘

Operator Precedence in Declarations

Understanding precedence is critical:

DECLARATION PRECEDENCE (highest to lowest):
===========================================

1. ( ) - Function call or grouping parentheses
2. [ ] - Array subscript
3. *   - Pointer dereference

EXAMPLES:
=========

int *p[10];        // Array of 10 pointers to int
                   // [] binds tighter than *
                   // Read: "p is array[10] of pointer to int"

int (*p)[10];      // Pointer to array of 10 ints
                   // () groups * with p first
                   // Read: "p is pointer to array[10] of int"

int *f();          // Function returning pointer to int
                   // () binds tighter than *
                   // Read: "f is function returning pointer to int"

int (*f)();        // Pointer to function returning int
                   // () groups * with f first
                   // Read: "f is pointer to function returning int"

Type Qualifiers and const Placement

The const keyword binds to what’s immediately to its left (or right if nothing is to its left):

CONST BINDING RULES:
====================

const int *p;        // Pointer to const int
                     // *p cannot be modified, p can

int const *p;        // Same as above (const after type)
                     // *p cannot be modified, p can

int * const p;       // Const pointer to int
                     // p cannot be modified, *p can

const int * const p; // Const pointer to const int
                     // Neither p nor *p can be modified

VISUALIZATION:
==============

const int * p;
──────┬──   ▲
      │     │
   modifies what p points to (the int)

int * const p;
    ▲ ──────┬
    │       │
    │    modifies p itself (the pointer)
    │
 the pointed-to int is not const

The Grammar (Simplified)

A simplified grammar for C declarations:

declaration     → specifiers declarator
specifiers      → (storage-class | type-qualifier | type-specifier)+
declarator      → pointer? direct-declarator
pointer         → '*' type-qualifier* pointer?
direct-declarator → identifier
                  | '(' declarator ')'
                  | direct-declarator '[' constant? ']'
                  | direct-declarator '(' parameter-list? ')'

Project Specification

What You Will Build

A command-line tool called cdecl that:

  1. Accepts a C declaration as input
  2. Parses it using the clockwise/spiral algorithm
  3. Outputs an English translation

Functional Requirements

  1. Basic Type Parsing:
    • Handle basic types: int, char, float, double, void
    • Handle signed/unsigned modifiers
    • Handle short/long modifiers
  2. Pointer Support:
    • Single and multiple levels of indirection (*, **, ***)
    • Const and volatile qualifiers on pointers
  3. Array Support:
    • Fixed-size arrays [10]
    • Unsized arrays []
    • Multi-dimensional arrays [10][20]
  4. Function Support:
    • Function declarations with parameter lists
    • Function pointers
    • Functions returning pointers
  5. Complex Combinations:
    • Arrays of pointers
    • Pointers to arrays
    • Pointers to functions
    • Functions returning pointers to arrays
  6. Error Handling:
    • Invalid syntax detection
    • Helpful error messages

Non-Functional Requirements

  • Performance: Parse declarations in under 10ms
  • Portability: Works on Linux, macOS, Windows
  • Code Quality: Clean, well-documented code
  • Testing: Comprehensive test suite

Real World Outcome

When complete, your tool will handle declarations like the famous signal function:

$ ./cdecl "void (*signal(int sig, void (*func)(int)))(int)"
signal is a function taking (int sig, pointer to function taking (int) returning void) returning pointer to function taking (int) returning void

$ ./cdecl "char *(*fp)(int, float)"
fp is a pointer to a function taking (int, float) returning pointer to char

$ ./cdecl "int (*(*callbacks[10])(int))(void)"
callbacks is an array[10] of pointer to function taking (int) returning pointer to function taking (void) returning int

$ ./cdecl "const char * const *pp"
pp is a pointer to const pointer to const char

$ ./cdecl "char *(*(*x)(void))[5]"
x is a pointer to function taking (void) returning pointer to array[5] of pointer to char

Solution Architecture

High-Level Design

┌──────────────────────────────────────────────────────────────┐
│                         CDECL TOOL                            │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  "char *(*fp)(int)"                                          │
│         │                                                    │
│         ▼                                                    │
│  ┌─────────────┐                                             │
│  │   LEXER     │──▶ [CHAR] [*] [(] [*] [ID:fp] [)] [(] ...   │
│  └─────────────┘                                             │
│         │                                                    │
│         ▼                                                    │
│  ┌─────────────┐     ┌──────────────────────────────┐        │
│  │   PARSER    │────▶│ AST:                         │        │
│  │  (Spiral)   │     │   POINTER TO                 │        │
│  └─────────────┘     │     FUNCTION (int)           │        │
│         │            │       RETURNING               │        │
│         │            │         POINTER TO            │        │
│         ▼            │           CHAR                │        │
│  ┌─────────────┐     └──────────────────────────────┘        │
│  │  GENERATOR  │                                             │
│  │  (English)  │                                             │
│  └─────────────┘                                             │
│         │                                                    │
│         ▼                                                    │
│  "fp is a pointer to function(int) returning pointer to char"│
│                                                              │
└──────────────────────────────────────────────────────────────┘

Data Structures

/* Token types for the lexer */
typedef enum {
    TOK_IDENTIFIER,
    TOK_TYPE,          /* int, char, void, etc. */
    TOK_QUALIFIER,     /* const, volatile */
    TOK_STORAGE,       /* static, extern, register */
    TOK_POINTER,       /* * */
    TOK_LPAREN,        /* ( */
    TOK_RPAREN,        /* ) */
    TOK_LBRACKET,      /* [ */
    TOK_RBRACKET,      /* ] */
    TOK_COMMA,         /* , */
    TOK_NUMBER,        /* array sizes */
    TOK_EOF,
    TOK_ERROR
} TokenType;

typedef struct {
    TokenType type;
    char value[64];
    int position;
} Token;

/* Declaration component types */
typedef enum {
    COMP_POINTER,      /* * */
    COMP_ARRAY,        /* [N] */
    COMP_FUNCTION,     /* (params) */
    COMP_CONST,        /* const */
    COMP_VOLATILE,     /* volatile */
} ComponentType;

typedef struct Component {
    ComponentType type;
    char data[256];    /* e.g., array size, parameter list */
    struct Component *next;
} Component;

/* Parsed declaration */
typedef struct {
    char identifier[64];
    char base_type[128];    /* e.g., "unsigned long int" */
    Component *components;   /* Stack of modifiers */
} Declaration;

Key Algorithms

Tokenization:

Algorithm: Tokenize(input)
1. Skip whitespace
2. If letter: read identifier/keyword, classify as TYPE/QUALIFIER/IDENTIFIER
3. If digit: read number (for array sizes)
4. If '*': emit POINTER token
5. If '(', ')', '[', ']', ',': emit corresponding token
6. Repeat until end of input

Spiral Rule Parsing:

Algorithm: ParseDeclarator(tokens)
1. Find the identifier (rightmost identifier in current context)
2. Read RIGHT from identifier:
   - If '(' for function: parse parameter list, record FUNCTION component
   - If '[' for array: parse size, record ARRAY component
   - If ')': stop going right (hit grouping paren)
3. Read LEFT from identifier:
   - If '*': record POINTER component
   - If 'const'/'volatile': attach to nearest pointer
   - If '(': this was grouping, expand scope and repeat from step 2
4. Continue until base type reached
5. Record base type (int, char, etc.)

English Generation:

Algorithm: GenerateEnglish(declaration)
1. Output: "{identifier} is "
2. For each component (in reverse order):
   - POINTER → "pointer to "
   - ARRAY[N] → "array[N] of "
   - FUNCTION(params) → "function taking (params) returning "
   - CONST → "const "
3. Output: base_type

Implementation Guide

Phase 1: Lexer (Day 1 Morning)

Goal: Convert input string to token stream.

/* lexer.h */
#ifndef LEXER_H
#define LEXER_H

#define MAX_TOKENS 100
#define MAX_TOKEN_LEN 64

typedef enum { /* ... as defined above ... */ } TokenType;
typedef struct { /* ... as defined above ... */ } Token;

typedef struct {
    Token tokens[MAX_TOKENS];
    int count;
    int current;
} TokenStream;

int tokenize(const char *input, TokenStream *stream);
Token peek(TokenStream *stream);
Token advance(TokenStream *stream);
int match(TokenStream *stream, TokenType expected);

#endif

Implementation hints:

  • Use isalpha() and isdigit() for character classification
  • Keep a table of keywords (int, char, const, etc.)
  • Store position for error messages
  • Handle both const int and int const

Phase 2: Parser Core (Day 1 Afternoon)

Goal: Implement the spiral rule parser.

Key insight: Use a stack to track modifiers as you spiral outward:

/* parser.h */
typedef struct {
    Component *stack;  /* Stack of modifiers found */
    char identifier[64];
    char base_type[128];
    int error;
    char error_msg[256];
} ParseResult;

ParseResult parse_declaration(TokenStream *tokens);

Parsing strategy:

  1. First pass: find the identifier
  2. Second pass: spiral outward collecting components
  3. Handle grouping parentheses by recursion

Phase 3: Complex Declarations (Day 2 Morning)

Goal: Handle nested declarations like function pointers.

The key challenge is handling declarations like:

void (*signal(int, void (*)(int)))(int)

Strategy:

  • When you hit ( after *, it could be:
    • Grouping: (*fp) - the * applies to fp
    • Function: *func() - function returning pointer
  • Distinguish by looking ahead: if identifier follows *, it’s grouping

Phase 4: English Generator (Day 2 Afternoon)

Goal: Convert parsed structure to readable English.

void generate_english(Declaration *decl, char *output, size_t size);

Walk the component stack and generate appropriate English text:

  • Keep track of whether you’ve started output
  • Handle singular/plural (“pointer” vs “pointers”)
  • Format parameter lists cleanly

Hints in Layers

Hint 1: Getting Started

Start with the simplest cases and verify with cdecl.org:

// Level 1: Single modifiers
"int x"               "x is int"
"int *p"              "p is pointer to int"
"int a[10]"           "a is array[10] of int"
"int f()"             "f is function returning int"

// Level 2: Two modifiers
"int *a[10]"          "a is array[10] of pointer to int"
"int (*p)[10]"        "p is pointer to array[10] of int"

Get these working before moving to complex cases.

Hint 2: Finding the Identifier

The identifier is always at the “core” of the declaration. Strategy:

  1. Skip any leading type specifiers
  2. Follow * symbols and ( parentheses
  3. The first identifier you hit is the one you want
// Identifier finding examples:
"int x"               x is after "int "
"int *p"              p is after "* "
"int (*fp)()"         fp is inside parentheses
"int *(*fp)()"        fp is in the deepest parentheses
Hint 3: Handling Precedence

The spiral works because of precedence. () and [] bind tighter than *.

// This is why:
int *a[10];    // a binds to [10] first, then to *
               // = array of pointers

int (*a)[10];  // ( ) groups * with a first
               // = pointer to array

When implementing, process right-side modifiers ((), []) before left-side (*).

Hint 4: Recursive Structure

Declarations are naturally recursive. When you see (*...), treat the contents as a sub-declaration:

int (*fp)();
     ^^^
     This is a declarator: "*fp"
     It means "fp is a pointer"

int (*(*fp)())();
      ^^^^^
      Inner declarator: "*fp"
      Then we wrap: "pointer to function returning..."

Use recursive descent: parse_declarator() calls itself when it encounters grouping parens.

Hint 5: Data Structure for Components

Use a linked list or array to store components in order:

// For: char *(*fp)(int, float)
// Components (inside out):
// 1. POINTER (from first *)
// 2. FUNCTION with params "int, float"
// 3. POINTER (from second *)
// 4. Base type: char

struct Component components[] = {
    { COMP_POINTER, "" },
    { COMP_FUNCTION, "int, float" },
    { COMP_POINTER, "" },
};
char base_type[] = "char";
Hint 6: English Generation Template

Generate English by walking components in reverse:

// Template:
// "{identifier} is {component_n} ... {component_1} {base_type}"

// For components:
// POINTER → "pointer to "
// ARRAY   → "array[SIZE] of "
// FUNCTION → "function taking (PARAMS) returning "
// CONST   → "const "
// VOLATILE → "volatile "

Testing Strategy

Test Categories

Category Purpose Examples
Unit Tests Test lexer and parser components Tokenization correctness
Integration Tests Test full pipeline Complete declarations
Regression Tests Ensure fixes don’t break Known tricky cases
Fuzz Tests Find edge cases Random valid inputs

Critical Test Cases

// Basic types
TEST("int x", "x is int")
TEST("char c", "c is char")
TEST("void v", "v is void")
TEST("float f", "f is float")
TEST("double d", "d is double")

// Pointers
TEST("int *p", "p is pointer to int")
TEST("int **pp", "pp is pointer to pointer to int")
TEST("int ***ppp", "ppp is pointer to pointer to pointer to int")

// Arrays
TEST("int a[10]", "a is array[10] of int")
TEST("int a[]", "a is array of int")
TEST("int a[10][20]", "a is array[10] of array[20] of int")

// Functions
TEST("int f()", "f is function returning int")
TEST("int f(int)", "f is function taking (int) returning int")
TEST("int f(int, char)", "f is function taking (int, char) returning int")

// Pointers and arrays
TEST("int *a[10]", "a is array[10] of pointer to int")
TEST("int (*a)[10]", "a is pointer to array[10] of int")

// Function pointers
TEST("int (*fp)()", "fp is pointer to function returning int")
TEST("int (*fp)(int, int)", "fp is pointer to function taking (int, int) returning int")

// Returning pointers
TEST("int *f()", "f is function returning pointer to int")
TEST("int **f()", "f is function returning pointer to pointer to int")

// Complex: function pointer returning pointer
TEST("char *(*fp)(int, float)",
     "fp is pointer to function taking (int, float) returning pointer to char")

// The signal declaration
TEST("void (*signal(int, void (*)(int)))(int)",
     "signal is function taking (int, pointer to function taking (int) returning void) returning pointer to function taking (int) returning void")

// Const variations
TEST("const int *p", "p is pointer to const int")
TEST("int const *p", "p is pointer to const int")
TEST("int * const p", "p is const pointer to int")
TEST("const int * const p", "p is const pointer to const int")

Testing Script

#!/bin/bash
# test_cdecl.sh

run_test() {
    input="$1"
    expected="$2"
    result=$(./cdecl "$input")
    if [ "$result" = "$expected" ]; then
        echo "PASS: $input"
    else
        echo "FAIL: $input"
        echo "  Expected: $expected"
        echo "  Got:      $result"
    fi
}

run_test "int x" "x is int"
run_test "int *p" "p is pointer to int"
run_test "int (*fp)()" "fp is pointer to function returning int"
# ... more tests ...

Common Pitfalls & Debugging

Frequent Mistakes

Pitfall Symptom Solution
Identifier confusion Wrong name extracted Look for rightmost identifier in deepest parens
Precedence errors int *a[10] parsed wrong Remember: [] binds tighter than *
Const placement const int* vs int* const Const binds left, or right if leftmost
Missing recursion Nested parens fail Call parser recursively on grouped content
Token lookahead Can’t distinguish cases Implement peek() to look ahead without consuming
Parameter parsing Function params wrong Parse params as comma-separated list

Debugging Strategies

  1. Print token stream: Verify lexer output
    void debug_tokens(TokenStream *s) {
        for (int i = 0; i < s->count; i++) {
            printf("[%d] %s: '%s'\n", i,
                   token_type_name(s->tokens[i].type),
                   s->tokens[i].value);
        }
    }
    
  2. Print parse tree: Visualize component structure
    void debug_components(Component *c, int depth) {
        while (c) {
            printf("%*s%s: %s\n", depth*2, "",
                   component_type_name(c->type), c->data);
            c = c->next;
        }
    }
    
  3. Compare with cdecl.org: Verify expected output
  4. Test incrementally: Add one feature at a time

Tricky Cases

// These look similar but are different:
int (*p)[10]    // p is pointer to array[10] of int
int *p[10]      // p is array[10] of pointer to int

// These need careful const handling:
const char *s     // pointer to const char (string literal safe)
char const *s     // same as above
char *const s     // const pointer to char (can modify string, not pointer)
const char *const s  // const pointer to const char

// Nested function pointers:
int (*(*fp)(int))(double)
// fp is pointer to function(int) returning pointer to function(double) returning int

Extensions & Challenges

Beginner Extensions

  • Interactive mode: Read declarations from stdin interactively
  • Reverse mode: Given English, generate C declaration
  • Typedef handling: Parse and expand typedef’d types
  • Colorized output: Highlight different parts of the declaration

Intermediate Extensions

  • Full C grammar: Handle struct, union, enum declarations
  • Variadic functions: Handle ... in parameter lists
  • Storage classes: Parse static, extern, register, auto
  • Declaration file: Parse entire .h files

Advanced Extensions

  • Modern C: Support C11/C17 features (_Atomic, _Generic)
  • C++ support: Handle references, templates (limited)
  • AST output: Generate machine-readable parse tree (JSON)
  • Error recovery: Continue parsing after errors

Real-World Connections

Industry Applications

  • Header analysis tools: Understanding API declarations
  • Documentation generators: Doxygen-style tools
  • IDE features: Declaration tooltips and navigation
  • Refactoring tools: Type-aware code transformation
  • Compiler frontends: First step in compilation
  • cdecl: The original C declaration explainer (1980s)
  • cgreen/cppcheck: C code analysis tools
  • clang-tidy: Uses AST analysis for code checking
  • LLVM/Clang: Full C parser implementation

Interview Relevance

This is a classic systems programming interview topic:

  1. “Explain what void (*signal(int, void (*)(int)))(int) means”
    • The signal function declaration from <signal.h>
    • Tests deep understanding of C syntax
  2. “What’s the difference between const char * and char const *?”
    • Tests understanding of const placement
    • Often followed by char * const
  3. “Write a function that takes a function pointer and returns a function pointer”
    • Tests practical declaration skills
    • Common in callback-heavy systems code
  4. “Parse a C declaration by hand on the whiteboard”
    • Demonstrate the spiral rule
    • Walk through step by step

Books That Will Help

Topic Book Chapter
Declaration syntax Expert C Programming Ch. 3 “Unscrambling Declarations in C”
C grammar The C Programming Language (K&R) Appendix A
Parsing techniques Compilers (Dragon Book) Ch. 4 “Syntax Analysis”
Type system C: A Reference Manual Ch. 4 “Declarations”
Modern C Effective C, 2nd Ed Ch. 3 “Types”

Self-Assessment Checklist

Understanding

  • I can explain why C declarations read “inside-out”
  • I can apply the clockwise/spiral rule to any declaration
  • I understand why int *a[10] differs from int (*a)[10]
  • I can explain where const binds in different positions
  • I understand the difference between specifiers and declarators

Implementation

  • My lexer correctly tokenizes all C declaration elements
  • My parser handles basic pointers, arrays, and functions
  • My parser handles nested function pointers
  • My parser handles const and volatile qualifiers
  • My English generator produces correct, readable output

Testing

  • All basic test cases pass
  • Complex cases like signal work correctly
  • Error handling provides helpful messages
  • I’ve verified against cdecl.org

Growth

  • I can read C declarations in real codebases confidently
  • I can write complex declarations correctly on the first try
  • I understand how this relates to typedef usage

Submission / Completion Criteria

Minimum Viable Completion

  • Handles basic types (int, char, void, float, double)
  • Parses single-level pointers and arrays
  • Parses simple function declarations
  • Produces readable English output

Full Completion

  • Handles all basic types with modifiers (unsigned, long, short)
  • Parses multi-level pointers and multi-dimensional arrays
  • Parses function pointers and functions returning pointers
  • Handles const and volatile qualifiers correctly
  • Comprehensive test suite passing

Excellence (Going Above & Beyond)

  • Handles the signal() declaration correctly
  • Interactive mode with readline support
  • Reverse mode (English to C)
  • Full C11 type support
  • Error recovery and helpful diagnostics

Thinking Exercise

Before writing code, trace through these declarations by hand. Write out each step of the spiral rule:

Exercise 1: char *argv[]

Step 1: Find identifier →
Step 2: Go right →
Step 3: Go left →
Step 4: Go left →
Result:

Exercise 2: int (*(*callbacks[10])(int))(void)

Step 1: Find identifier →
Step 2: Go right →
Step 3: Go left →
Step 4: Continue spiral... →
Result:

Exercise 3: const char * const * const pp

Step 1: Find identifier →
Step 2: Trace const binding →
Result:

Interview Questions They’ll Ask

After completing this project, you’ll be ready for:

  1. “What is the clockwise/spiral rule?”
    • Explain the algorithm step by step
    • Demonstrate with an example
  2. “Parse void (*f(int, void (*)(int)))(int) and explain it”
    • This is the signal() signature
    • Walk through the spiral systematically
  3. “What’s the difference between int *const p and const int *p?”
    • Explain const binding rules
    • Give practical examples of when each is used
  4. “Why does C use this declaration syntax?”
    • “Declaration follows use” principle
    • Historical context from B and BCPL
  5. “How would you implement a parser for C declarations?”
    • Discuss tokenization approach
    • Explain recursive descent for nested structures
    • Mention precedence handling

This guide was expanded from EXPERT_C_PROGRAMMING_DEEP_DIVE.md. For the complete learning path, see the project index.