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);
}
}
| Celc | Farhenheit |
| 100 | 212.0 |
| 80 | 176.0 |
| 60 | 140.0 |
| 40 | 104.0 |
| 20 | 68.0 |
| 0 | 32.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);
}- 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.
- 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.
- 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
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