K&R2 exercise 6-5

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 exercise 6-5: write a function undef that will remove a name and
//definition from the table maintained by lookup and install.
//
//Removing a node from a chain means: whatever points AT the node must be
//made to point PAST it, to node->next. The trouble is that "whatever
//points at it" is one of two different things:
//
//  - the node is first in its chain:   hashtab[h] points at it
//  - the node is further along:        the previous node's next does
//
//                 hashtab[18]          Hm's next
//                     |                    |
//                     v                    v
//  hashtab[18] --> {Hm, next} ----------> {IN, next} --> NULL
//
//The usual answer is to remember the previous node while walking, and
//write if (prev == NULL) hashtab[h] = ...; else prev->next = ...;
//
//undef below uses a pointer to a POINTER instead: pp holds the address
//of the arrow itself -- first &hashtab[h], then &node->next for each
//node. Both are just "a struct nlist * stored somewhere", so one
//statement, *pp = np->next, unlinks the first node and a middle node
//alike. No prev, no special case.
//
//main installs IN and Hm (both in chain 18, Hm at the head) and OUT,
//then removes a middle node, a head node, and a name that isn't there.
#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 *);
int undef(char *);
char *mystrdup(char *);
void dump(void);

int main(void)
{
    install("IN", "1");
    install("OUT", "0");
    install("Hm", "oops");          //same chain as IN, goes in front of it
    dump();

    printf("\nundef(\"IN\")  = %d   (middle of chain 18)\n", undef("IN"));
    dump();

    printf("\nundef(\"Hm\")  = %d   (head of chain 18)\n", undef("Hm"));
    dump();

    printf("\nundef(\"EOF\") = %d   (never installed)\n", undef("EOF"));
    printf("lookup(\"IN\") = %s\n", lookup("IN") == NULL ? "NULL" : "found");
    return 0;
}

//undef:  remove name and its definition from hashtab;
//        return 1 if it was there, 0 if not
int undef(char *name)
{
    struct nlist **pp;      //address of the pointer that points at a node
    struct nlist *np;

    //IDIOM: pointer to pointer for list removal -- pp starts at the
    //address of the chain's head, then moves to the address of each
    //node's next; *pp is always "the arrow pointing at the current node"
    for (pp = &hashtab[hash(name)]; *pp != NULL; pp = &(*pp)->next)
        if (strcmp(name, (*pp)->name) == 0) {
            np = *pp;
            *pp = np->next;     //the arrow now skips np: np is unlinked
            //IDIOM: free in reverse of allocation -- the strings first,
            //while np still exists to reach them, then np itself
            free(np->name);
            free(np->defn);
            free(np);
            return 1;
        }
    return 0;
}

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

    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;

    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;

    if ((np = lookup(name)) == NULL) {      //not found
        np = (struct nlist *) malloc(sizeof(*np));
        if (np == NULL || (np->name = mystrdup(name)) == NULL)
            return NULL;
        hashval = hash(name);
        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;

    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:~/webdev-projects$ gcc -Wall -Wextra cc-c.c -o cc-c
win@DESKTOP-MEIH88T:~/webdev-projects$  ./cc-c
[ 18] --> Hm=oops --> IN=1 --> NULL
[ 60] --> OUT=0 --> NULL

undef("IN")  = 1   (middle of chain 18)
[ 18] --> Hm=oops --> NULL
[ 60] --> OUT=0 --> NULL

undef("Hm")  = 1   (head of chain 18)
[ 60] --> OUT=0 --> NULL

undef("EOF") = 0   (never installed)
lookup("IN") = NULL