K&R2 6.6 example

2026-10-11

#include <stdio.h>
#include <string.h>     //for strcmp(), strlen(), strcpy()
#include <stdlib.h>     //for malloc(), free()
//compile with: gcc -Wall -Wextra cc-c.c -o cc-c
//run with:     ./cc-c

//K&R2 6.6 Table Lookup: the symbol table of a #define processor.
//
//install(name, defn)  remember that name stands for defn
//lookup(s)            find s's entry, or NULL
//
//hashtab is an array of 101 POINTERS, each the head of a linked list
//("chain"). hash(s) picks the chain; strcmp picks the entry within it.
//
//  hashtab
//   [0]  NULL
//   ...
//   [18] --> {next, "Hm", "oops"} --> {next, "IN", "1"} --> NULL
//   ...
//
//main installs a few names and prints every non-empty chain, to show:
//  - "IN" and "Hm" hash to the same slot (31*73+78 == 31*72+109 == 2341,
//    and 2341 % 101 == 18), so they share chain 18
//  - new entries go at the HEAD of a chain, so "Hm" comes before "IN"
//  - installing "IN" again replaces its defn; no new entry is made
//
//The book's strdup is called mystrdup here: <string.h> already declares
//a strdup of its own, and a second definition would clash with it.
#define HASHSIZE 101

struct nlist {              //table entry:
    struct nlist *next;     //next entry in chain
    char *name;             //defined name
    char *defn;             //replacement text
};

static struct nlist *hashtab[HASHSIZE];     //pointer table, all NULL

unsigned hash(char *);
struct nlist *lookup(char *);
struct nlist *install(char *, char *);
char *mystrdup(char *);
void dump(void);

int main(void)
{
    struct nlist *np;

    install("IN", "1");
    install("OUT", "0");
    install("MAXLINE", "1000");
    install("Hm", "oops");          //collides with "IN"
    printf("hash(\"IN\") = %u   hash(\"Hm\") = %u\n\n", hash("IN"), hash("Hm"));
    dump();

    install("IN", "2");             //redefine: same entry, new defn
    printf("\nafter install(\"IN\", \"2\"):\n");
    dump();

    printf("\n");
    if ((np = lookup("MAXLINE")) != NULL)
        printf("lookup(\"MAXLINE\") -> %s\n", np->defn);
    if ((np = lookup("EOF")) == NULL)
        printf("lookup(\"EOF\")     -> NULL (never installed)\n");
    return 0;
}

//hash:  form hash value for string s
unsigned hash(char *s)
{
    unsigned hashval;

    //IDIOM: walk a string by moving the pointer itself -- s++ steps to
    //the next char, *s != '\0' stops at the terminator; no index needed
    for (hashval = 0; *s != '\0'; s++)
        hashval = *s + 31 * hashval;
    return hashval % HASHSIZE;
}

//lookup:  look for s in hashtab
struct nlist *lookup(char *s)
{
    struct nlist *np;

    //IDIOM: walk a linked list -- for (p = head; p != NULL; p = p->next)
    //is the list version of for (i = 0; i < n; i++)
    for (np = hashtab[hash(s)]; np != NULL; np = np->next)
        if (strcmp(s, np->name) == 0)
            return np;      //found
    return NULL;            //not found
}

//install:  put (name, defn) in hashtab
struct nlist *install(char *name, char *defn)
{
    struct nlist *np;
    unsigned hashval;

    //IDIOM: assign and test in one go, like while ((c = getchar()) != EOF);
    //the inner parens matter: = binds more loosely than ==
    if ((np = lookup(name)) == NULL) {      //not found
        //IDIOM: sizeof(*np) -- the size of whatever np points to; sizeof
        //never evaluates *np, so using the not-yet-set np is safe
        np = (struct nlist *) malloc(sizeof(*np));
        //IDIOM: guard with || -- the right side only runs if np != NULL,
        //so np->name is never touched through a null pointer
        if (np == NULL || (np->name = mystrdup(name)) == NULL)
            return NULL;
        hashval = hash(name);
        //IDIOM: insert at the head of a list -- link to the old head
        //FIRST, then make the new node the head; swap them and the
        //rest of the chain is lost
        np->next = hashtab[hashval];        //new entry points at old head
        hashtab[hashval] = np;              //and becomes the new head
    } else                                  //already there
        free((void *) np->defn);            //free previous defn
    if ((np->defn = mystrdup(defn)) == NULL)
        return NULL;
    return np;
}

//mystrdup:  make a duplicate of s (the book's strdup)
char *mystrdup(char *s)
{
    char *p;

    //IDIOM: malloc(strlen(s) + 1) -- strlen doesn't count the '\0',
    //so a string copy always needs one byte more than its length
    p = (char *) malloc(strlen(s) + 1);     //+1 for '\0'
    if (p != NULL)
        strcpy(p, s);
    return p;
}

//dump:  print every non-empty chain, head first
void dump(void)
{
    int i;
    struct nlist *np;

    for (i = 0; i < HASHSIZE; i++)
        if (hashtab[i] != NULL) {
            printf("[%3d]", i);
            for (np = hashtab[i]; np != NULL; np = np->next)
                printf(" --> %s=%s", np->name, np->defn);
            printf(" --> NULL\n");
        }
}

win@DESKTOP-MEIH88T:~$ cd webdev-projects
win@DESKTOP-MEIH88T:~/webdev-projects$ gcc -Wall -Wextra cc-c.c -o cc-c
win@DESKTOP-MEIH88T:~/webdev-projects$ ./cc-c
hash("IN") = 18   hash("Hm") = 18

[ 18] --> Hm=oops --> IN=1 --> NULL
[ 60] --> OUT=0 --> NULL
[ 73] --> MAXLINE=1000 --> NULL

after install("IN", "2"):
[ 18] --> Hm=oops --> IN=2 --> NULL
[ 60] --> OUT=0 --> NULL
[ 73] --> MAXLINE=1000 --> NULL

lookup("MAXLINE") -> 1000
lookup("EOF")     -> NULL (never installed)