Class 9 — Tuesday, September 22
1 Two dates
A4 is due tomorrow, Wednesday, September 23. The box near the top of it says what to get to first if you are out of time.
The practice test is Thursday, September 24, and it counts for nothing. It is built to match Midterm Part 1, which is the Tuesday after — same room, same length, same number of programs, same upload. It is the only way to find out where you stand while there is still time to do something about it.
2 A string is an array with one extra byte on the end
#include <stdio.h>
#include <string.h>
int main() {
char word[] = "hello";
printf("strlen says %d\n", (int) strlen(word));
printf("sizeof says %d\n", (int) sizeof(word));
}strlen says 5
sizeof says 6
Five letters. Six bytes. The sixth one is not a letter — it is '\0', the byte that says stop, and every function in string.h is looking for it.
Notice what strlen was not given: a length. Nobody told it how long word is. It starts at the front and walks forward until it finds that byte, counting as it goes.
So a loop written as for (int i = 0; i < strlen(word); i++) sends strlen down the whole word again on every turn — once per letter. Walk to the byte yourself instead:
for (int i = 0; word[i] != '\0'; i++)2.1 Now take the byte away
#include <stdio.h>
#include <string.h>
int main() {
char word[] = "hello";
word[5] = '!';
printf("%s\n", word);
printf("strlen says %d\n", (int) strlen(word));
}Compile it with gcc -Wall. Read what the compiler says about it. Then run it three times.
Whatever number strlen gives you, it is not 5. It may not be the same number twice.
2.2 The name for it: UNDEFINED BEHAVIOR (a.k.a. UB)
The program is wrong. Writing over the '\0' is a bug — every time, on every machine. What is undefined is not whether it is wrong. It is what happens when you run it, and there the language refuses to say anything at all.
It does not have to crash. So “it worked when I ran it” tells you nothing.
3 Build it, then build it again
Note: If you have been stuck for three minutes, put your hand up.
This is not the program that is due tomorrow — different files, different functions. Work in a new directory, not your A4 one.
You are making a program called count out of three files and a Makefile.
3.1 Round 1 — in pairs, with this page open
One keyboard between two people. Swap who types when you get to the Makefile.
count.h — the prototypes, with an include guard:
#ifndef COUNT_H
#define COUNT_H
int total(int arr[], int n);
int length(char s[]);
#endifcount.c — the two definitions, and no main:
#include "count.h"
int total(int arr[], int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
return sum;
}
int length(char s[]) {
int n = 0;
while (s[n] != '\0') {
n++;
}
return n;
}main.c — main and nothing else:
#include <stdio.h>
#include "count.h"
#define SIZE 5
int main() {
int marks[SIZE] = {90, 72, 88, 61, 100};
char name[] = "commonwealth";
printf("total is %d\n", total(marks, SIZE));
printf("length is %d\n", length(name));
}Makefile — four rules. The whitespace before each gcc and before rm is one tab character, not spaces. With spaces, make says missing separator.
count: main.o count.o
gcc -Wall -o count main.o count.o
main.o: main.c count.h
gcc -Wall -c main.c
count.o: count.c count.h
gcc -Wall -c count.c
clean:
rm -f count main.o count.o
-c means compile this file and stop. It makes main.o, which you cannot run. Only the first rule makes something you can run, and it names no .c file at all.
Done when make builds it, ./count prints two numbers, and you can name the file make did not rebuild after this:
touch count.c
make
Skipping it saves you nothing on a program this small. On a large one it is the whole reason make exists.
3.2 Round 2 — on your own, with this page closed
mkdir round2
cd round2
Then close this page, split up from your partner, and write the same four files again from nothing. Fifteen minutes.
Delete nothing. Round 1 stays exactly where it is.
You are allowed to look something up. Before you do, write down what it was you could not remember — that is the part worth having on Thursday.
4 Practice problems
Everything from here on is for when Round 2 is done. In order, and stop wherever you get to.
Each of these is one more function in the same library, so each one goes in three places — a prototype in count.h, a definition in count.c, a call in main.c. That is the thing three files make harder than one file. Run make after each and read what it rebuilt — every one of these touches count.h, so every one rebuilds both.
Check each one with assert, the way the design recipe says and the way A3 did. Add #include <assert.h> to main.c, and end main with:
printf("all examples passed\n");One assert is given for each function below. Write at least two more of your own for each, and make one of them awkward — the empty word, a one-element array, a value at the very start or the very end.
smallest(int arr[], int n)— the smallest number in the array.assert(smallest(marks, SIZE) == 61);vowels(char s[])— counts the vowels in a word, walking to the'\0'the waylengthdoes — noti < strlen(s).assert(vowels(name) == 4);average(int arr[], int n)— the average of the array.assert(average(marks, SIZE) == 82);The exact average is
82.2and this returns82. For your awkward case, take two numbers whose average ends in.5—{1, 2}— and write down what you expect before you run it.copy(char from[], char to[])— copies a string into another array one character at a time. You need somewhere to put it, so addchar buf[20];tomain.copy(name, buf); assert(length(buf) == length(name));Then delete the line that writes the
'\0'and run it again.Then put it back, and copy a word with sixty letters into
buf. Write down what you expect before you run it. Whatever it did, add two asserts about two things the copy never touched, and run it once more:assert(length(name) == 12); assert(total(marks, SIZE) == 411);
5 A better Makefile
Four rules for two files. Ten files would be twenty rules. Put this in a second file called Makefile.wild — leave your working Makefile alone:
CFLAGS = -Wall
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
HDRS = $(wildcard *.h)
count: $(OBJS)
gcc $(CFLAGS) -o count $(OBJS)
%.o: %.c $(HDRS)
gcc $(CFLAGS) -c $<
clean:
rm -f count $(OBJS)
CFLAGS is an ordinary variable, there so that -Wall is written once. Then three new things. $(wildcard *.c) is every .c file in this directory, and $(wildcard *.h) is every header. $(SRCS:.c=.o) is that list with each .c swapped for .o. And %.o: %.c $(HDRS) is one rule standing in for all of them — each .o from its own .c, and every one of them from every header. $< means the first file on the right of the colon.
Run it with -f, which tells make to use a different file:
make -f Makefile.wild clean
make -f Makefile.wild
Done when it runs the same commands your four-rule version did — in a different order, which is fine — and touch count.c still rebuilds only count.o.
Then the part worth the trouble. Take one of the functions you just wrote and move it out of count.c into a file of its own, extra.c. Prototype stays in count.h, the call stays in main.c. Now build it both ways:
make
make -f Makefile.wild
One of them needs you to edit it first. The other already knows. Read the error the first one gives you — it is not the compiler complaining.
6 Then exercises that mark themselves
Both run in the browser and nothing is collected.
- Arrays and Strings — the book’s own questions on this week’s reading.
- learn-c.org — short lessons with an editor on the page. Arrays and Strings are the two for today.
7 Before you leave
Close everything except your Brightspace Journal.
Both directories are worth keeping. Copy them somewhere you can reach from another seat — these machines keep your files, but they keep them on this machine, and on Thursday you may not be in this chair.