Kevin De Baerdemaeker

The C Programming Language

Modified 2025-09-24

C is statically, but weakly typed language. The types are statically typed due to the compiler, but weakly typed as it assumes the programmer knows what they are doing.

CH1: Tutorial Introduction

p14 Print the Celcius to Farhrenheit conversion table in reverse order.

The fractional part of two integer divisions is thrown away: 9/5 = 1. Use floats.

#define UPPER 100
#define LOWER 0
#define STEP 20

int main() {
  printf("Celc Farhenheit\n");
  for (int C = UPPER; C >= LOWER; C -= STEP) {
    printf("%3d %6.1f\n", C, (C * 9.0 / 5.0) + 32);
  }
}
CelcFarhenheit
100212.0
80176.0
60140.0
40104.0
2068.0
032.0

p17 What's the value of EOF?

The EOF value is -1, which is why getChar() is represented by an int instead of char.

p20 Count blanks, tabs and newlines

I can't use :cmdline argument of the code block, because it sends it on as arguments, so getchar doesn't do anything in org-mode code blocks.

int bc = 0;
int c;

while ((c = getchar()) != EOF){
  if (c == ' ' || c == '\t' || c == '\n'){
    ++bc;
  }
 }
printf("Solution: %d\n", bc);

p20 Counting words

#define IN 1    /* inside a word */
#define OUT 0   /* outside a word */

/* count lines, words, and characters in input */
int main()
{
  int state = OUT;
  int c, nc, nl, nw;

  nc = nl = nw = 0;
  while ((c = getchar()) != EOF) {
    ++nc;
    if (c == '\n'){
      ++nl;
    }
    if (c != ' ' && c != '\t' && c != '\n'){
      if (state == OUT){
	++nw;
      }
      state = IN;
    }else{
      state = OUT;
    }
  }
  printf("characters: %d\n", nc);
  printf("lines:      %d\n", nl);
  printf("words:      %d\n", nw);
}

p21 Write a program that prints its input one word per line

int c;
int isWord = 0;

while ((c = getchar()) != EOF) {
  if (c == ' ' || c == '\t' || c == '\n') {
    if (isWord == 0) {
      isWord = 1;
      putchar('\n');
    }
  } else {
    isWord = 0;
    putchar(c);
  }
 }

return 0;

P24 Write a program to print a histogram of the frequencies of different characters

#define LOWER_LIMIT 32
#define UPPER_LIMIT 126
#define MAX_CHARS UPPER_LIMIT - LOWER_LIMIT + 1

int main() {
  int c;
  int charFreq[MAX_CHARS];

  for (int i = 0; i < MAX_CHARS; i++) {
    charFreq[i] = 0;
  }

  while ((c = getchar()) != EOF) {
    if (c >= LOWER_LIMIT && c <= UPPER_LIMIT) {
      charFreq[c - LOWER_LIMIT]++;
    }
  }

  for (int i = 0; i < MAX_CHARS; i++) {
    printf("\n%c:\t", i + LOWER_LIMIT);

    for (int j = 0; j < charFreq[i]; j++) {
      putchar('|');
    }
  }

  return 0;
}

TODO p31 Write a function reverse(s). Use it to write a program that reverses its input per line

The exercise 1-19 combines the previous two, so let's build this one.

#include <stdio.h>
#include <stdlib.h>

#define MAXLINE 1000

void reverseLine(char s[]);
<<srcMyGetline>>

int main() {
  int len;
  char line[MAXLINE];

  while ((len = myGetline(line, MAXLINE)) > 0) {
    reverseLine(line);
    printf("%s", line);
  }

  return 0;
}

void reverseLine(char s[]) {
  char tmp;
  int i = 0;

  while (s[i] != '\0')
    i++;

  --i;

  if (s[i] == '\n')
    --i;

  int j = 0;

  while (j < i) {
    tmp = s[j];
    s[j] = s[i];
    s[i] = tmp;

    --i;
    ++j;
  }
}

For example, a solution I saw was the following. Need to check a bit deeper how this works. The arguments of a function are ALWAYS the actual values, rather than a reference to the original value. If the function is supposed to modify the original value outside the function, the caller must provide a pointer to the value.

Passing an array to a function always passes the address of the array, as you'd otherwise need to duplicate the memory.

#define MAXLINE 1000

void reverse(char* s, int len) {
    int i = 0;
    int j = len - 1;
    while (i < j) {
        char temp = s[i];
        s[i] = s[j];
        s[j] = temp;
        i++;
        j--;
    }
}

int main() {
    char line[MAXLINE];
    int length;

    while (fgets(line, MAXLINE, stdin) != NULL) {
        // Compute length manually to avoid extra passes
        for (length = 0; line[length] != '\n' && line[length] != '\0'; length++);

        reverse(line, length);
        printf("%.*s\n", length, line); // print only up to the line length
    }

    return 0;
}

p34 Replace blank strings with tabs and blanks to achieve same spacing

#define TAB_IN_SPACES 4

int main() {
  int c;
  int spaces = 0;

  while ((c = getchar()) != EOF) {
    if (c == ' ') {
      ++spaces;

      if (spaces == TAB_IN_SPACES) {
        spaces = 0;
        putchar('\t');
      }
    } else {
      for (; spaces != 0; --spaces) {
        putchar(' ');
      }
      putchar(c);
    }
  }

  return 0;
}

TODO p34 Fold long input lines into shorter lines, do something intelligent in case there is no blanks

#include <stdio.h>
#define FOLD_AT 12

char line[FOLD_AT];

int getBlank(int pos);
int updateLine(int pos);
void printLine(int pos);

int main() {
  int pos = 0;
  int c;

  while ((c = getchar()) != EOF) {
    line[pos] = c;

    if (c == '\n') {
      printLine(pos);
      pos = 0;
    } else {
      ++pos;

      if (pos >= FOLD_AT) {
        pos = getBlank(pos);
        printLine(pos);
        pos = updateLine(pos);
      }
    }
  }

  return 0;
}

int getBlank(int pos) {
  while (pos > 0 && line[pos] != ' ') {
    --pos;
  }

  return pos == 0 ? FOLD_AT : pos + 1;
}

void printLine(int pos) {
  for (int j = 0; j < pos; j++) {
    putchar(line[j]);
  }

  if (pos > 0) {
    putchar('\n');
  }
}

int updateLine(int pos) {
  int i = 0;
  for (int j = pos; j < FOLD_AT && j > 0; j++) {
    line[i] = line[j];
    i++;
  }

  return i;
}

Why this is more “data-oriented”

Sliding window / minimal memory movement

Instead of copying characters every fold, we just move start forward.

No repeated updateLine() loops or memmove() unless really needed.

Linear memory access

All reads/writes are sequential.

Works well with CPU caches.

Scalable

Large buffer allows handling very long lines efficiently.

Folding decisions made in-place without extra memory.

Predictable state

Only start and end indices track current content.

Minimal temporary variables.

#include <stdio.h>
#include <string.h>

#define FOLD_AT 12
#define BUFFER_SIZE 1024  // large enough for most lines

int main() {
    char buffer[BUFFER_SIZE];
    int start = 0;   // start of current line in buffer
    int end = 0;     // next free position in buffer
    int c;

    while ((c = getchar()) != EOF) {
        buffer[end++] = c;

        // If newline, print the line and reset
        if (c == '\n') {
            fwrite(&buffer[start], 1, end - start, stdout);
            start = end = 0;
        }

        // Fold if line exceeds FOLD_AT
        while (end - start >= FOLD_AT) {
            // Find last blank/tab before FOLD_AT
            int fold_pos = FOLD_AT - 1;
            while (fold_pos > 0 && buffer[start + fold_pos] != ' ' && buffer[start + fold_pos] != '\t') {
                fold_pos--;
            }

            if (fold_pos == 0) {
                // No blank found, fold at FOLD_AT
                fold_pos = FOLD_AT;
            } else {
                fold_pos++; // include the blank in the fold
            }

            fwrite(&buffer[start], 1, fold_pos, stdout);
            putchar('\n');

            // Move start forward, leave remaining chars in buffer
            start += fold_pos;

            // If buffer has been fully printed, reset indices
            if (start == end) {
                start = end = 0;
            }
        }
    }

    // Print any remaining characters
    if (end > start) {
        fwrite(&buffer[start], 1, end - start, stdout);
    }

    return 0;
}

TODO p34 Remove all comments from a C program

#include <stdio.h>

void searchComment(char c);
void ignoreInput(void);
void ignoreComments(char c);

int main() {
  int c;

  while ((c = getchar()) != EOF) {
    searchComment(c);
  }

  return 0;
}

void searchComment(char c) {
  int d;

  if (c == '/') {
    if ((d = getchar()) == '*') {
      ignoreInput();
    } else if (d == '/') {
      putchar(c);
      searchComment(d);
    } else {
      putchar(c);
      putchar(d);
    }
  } else if (c == '\'' || c == '"') {
    ignoreComments(c);
  } else {
    putchar(c);
  }
}

void ignoreInput() {
  int c = getchar();
  int d = getchar();

  while (c != '*' || d != '/') {
    c = d;
    d = getchar();
  }
}

void ignoreComments(char c) {
  int d;

  putchar(c);
  while ((d = getchar()) != c) {
    putchar(d);

    // skip the potential early return of quoted string when in the string
    // quotes are escaped
    // printf("\" /* I will not go away */ \"")
    if (d == '\\') {
      putchar(getchar());
    }
  }

  putchar(d);
}
  1. What your code does

main() reads input character by character and calls searchComment(c).

searchComment(c) handles:

C-style comments * ... * → delegates to ignoreInput().

C++-style single-line comments // ... → passes through the / and recursively continues.

Quoted strings and character constants '...' or "..." → delegates to ignoreComments(c).

All other characters → printed immediately.

ignoreInput() skips everything inside a * ... * comment.

ignoreComments(c) prints quoted strings/characters as-is, handling escaped quotes.

✅ Handles nested quotes and escaped characters correctly.

  1. Beginner Perspective

Uses character-by-character parsing, which is simple to understand.

Recursion (searchComment(d)) may be tricky for a beginner — could be replaced by loops.

Separation of concerns is good: different functions handle comments vs. quoted strings.

Limitations a beginner might hit:

Edge cases like * comment *" or "\/*" may require careful attention.

Handling multi-character lookahead with getchar() can be confusing.

  1. Data-Oriented / Casey Muratori Perspective

This is an interesting example for DOD thinking, because:

Input is a contiguous stream of characters

You’re reading and writing sequentially — perfect for linear memory processing.

Minimal state tracking

Only the current character (and sometimes the next d) is tracked.

No need to store the whole program in memory. ✅

Opportunities for performance / DOD improvements

Avoid recursion (searchComment(d)) → use a simple loop to prevent stack overhead for long streams.

Process in blocks instead of single characters for very large files → reduces I/O overhead.

Treat quoted strings and comments as contiguous memory blocks for faster skipping/printing.

#include <stdio.h>

int main() {
    int c, next;
    enum { NORMAL, SLASH, IN_COMMENT, IN_LINE_COMMENT, IN_STRING, IN_CHAR } state = NORMAL;

    while ((c = getchar()) != EOF) {
        switch (state) {
            case NORMAL:
                if (c == '/') {
                    state = SLASH;
                } else if (c == '"') {
                    putchar(c);
                    state = IN_STRING;
                } else if (c == '\'') {
                    putchar(c);
                    state = IN_CHAR;
                } else {
                    putchar(c);
                }
                break;

            case SLASH:
                if (c == '*') {
                    state = IN_COMMENT;
                } else if (c == '/') {
                    state = IN_LINE_COMMENT;
                } else {
                    putchar('/');
                    putchar(c);
                    state = NORMAL;
                }
                break;

            case IN_COMMENT:
                if (c == '*') {
                    next = getchar();
                    if (next == '/') {
                        state = NORMAL;
                    } else if (next != EOF) {
                        ungetc(next, stdin);
                    }
                }
                break;

            case IN_LINE_COMMENT:
                if (c == '\n') {
                    putchar(c);
                    state = NORMAL;
                }
                break;

            case IN_STRING:
                putchar(c);
                if (c == '\\') {
                    putchar(getchar()); // escaped character
                } else if (c == '"') {
                    state = NORMAL;
                }
                break;

            case IN_CHAR:
                putchar(c);
                if (c == '\\') {
                    putchar(getchar()); // escaped character
                } else if (c == '\'') {
                    state = NORMAL;
                }
                break;
        }
    }

    return 0;
}

TODO p34 Rudimentary syntax checker, like paranetheses, brackets and space

External Variables

CH4:

Metadata

Creator(s)

Brian W. Kernighan

  • Source
  • Recommended By

    Reason

    I'd like to dive into C and learn about lower level languages, and know enough about low level programming to start develop my own games from scratch