Assignment 4 — Arrays, strings, and the end of the array

Five small programs about arrays and strings — including one that reads past the end on purpose — and then the useful half of them moved into a three-file program with a Makefile.

Due Sep 23, 2026

A3 gave your code names. This week it gets collections: a hundred numbers under one name, and a word as a thing you can pass around.

The one sentence this week is built on is in the reading, and it is the promise made on the first day of this course. Java throws ArrayIndexOutOfBoundsException. C does not check at all — not when it compiles your program, and not when it runs it. Step 2 is you finding out what happens instead, on purpose, with the compiler watching and saying nothing.

Start a new directory for this assignment. Not the A3 one. The last step builds everything in the directory at once.

You will hand in ten files: stats.c, offend.c, modify.c, word.c, shout.c, text.h, text.c, main.c, Makefile and notes.txt.

If the week runs out, get to 1, 2 and 6

Step 2 is the one to do while you are fresh, not at eleven at night — it is short, it asks you to guess before you run anything, and a guess you make in a hurry is not worth writing down.

Step 6 is the one Midterm Part 1 is most likely to ask you for, and it is the step with an order that has to be right. Doing it once this week is worth more than reading about it twice next week.

Steps 3, 4 and 5 are the drill. They are short and they are where the actual fluency comes from, so do them if you have the evening.

The lab machines in Ben Franklin 103 are the guaranteed environment. Compile everything there before you submit, even if you wrote it elsewhere.

Submit early and submit again. Brightspace keeps the latest version, so upload what you have on Friday and replace it on Tuesday.

AI: the course AI policy applies in full. No AI-generated code, and no asking a chatbot to solve these before the deadline. The optional problems at the end say when that changes.

The shapes, for reference

Keep this next to you. [DIS] 16.5 — Arrays and Strings is the reading that explains it, and it is due before Tuesday.

An array is declared with a type, a name, and a size in square brackets. The size is fixed for the life of the variable:

int scores[10];

That gives you ten ints, at positions 0 through 9. You can also declare and fill it at once, and then the size can be left out:

int scores[] = {4, 8, 15, 16, 23, 42};

The array does not know how long it is. There is no scores.length and there is no len(scores). If a piece of code needs the length, some variable has to be carrying it:

int scores[] = {4, 8, 15, 16, 23, 42};
int n = 6;

A for loop over an array is always the same shape, and the < is doing real work — with <= you would touch position n, which is one past the end:

for (int i = 0; i < n; i++) {
    printf("%d\n", scores[i]);
}

A string is an array of char with a '\0' on the end. That last byte is not decoration: it is the only way any function can find where the string stops. "hello" occupies six bytes, not five.

char word[100];
scanf("%99s", word);
printf("%s has %d letters\n", word, (int) strlen(word));

strlen needs #include <string.h>. Two details in that scanf are worth noticing now and are explained in step 4: there is no & in front of word, and the 99 is not optional.

Part A — arrays

  1. stats.c — ten numbers and three answers. Declare int values[10]. Read ten numbers into it with a for loop and scanf, then print three things: their sum, the largest, and the average.

    If you were in class on Thursday, you have most of this already. Finish it here.

    The average is the one with a trap in it. sum / 10 where both are int gives you an int, so the average of ten numbers ending in a 7 quietly loses its fraction. Print it with %f and make the division produce a real number — (double) sum / 10 is one way.

    In notes.txt: what did the average print before you fixed the division, for an input whose true average is not a whole number? One line.

  2. offend.c — the one that reads past the end.

    #include <stdio.h>
    
    int main() {
        int a[5] = {10, 20, 30, 40, 50};
        printf("a[4] is %d\n", a[4]);
        printf("a[5] is %d\n", a[5]);
        printf("a[6] is %d\n", a[6]);
        printf("a[7] is %d\n", a[7]);
    }

    Write down your prediction before you compile it. Three questions, and “I don’t know” is an answer to any of them, as long as it is written down first:

    1. Does gcc -Wall complain? If so, what about?
    2. Does the program crash?
    3. If it does not crash, what do the last three lines print?

    Now compile it with gcc -Wall offend.c -o offend and run it. Then run it five more times without recompiling. Record every answer in notes.txt, beside your prediction.

    Look carefully at whether the six runs agree with each other. Some of those positions will give you the same number every time and some will not. Say in notes.txt which did which. That difference is undefined behavior — the reading’s phrase, happening in front of you. The language declines to say what is there, so nothing has to be consistent: not between machines, and not between two runs of one binary.

    Then add a line printing a[1000], and another printing a[-1]. Rebuild and run. In notes.txt, say what each one did, including if one of them crashed.

    Finally, two or three sentences: in Java, how far would this program have got? You do not need the exact exception name. The point is the difference in when you find out.

    Do not try to make this program correct. Being wrong is its entire job.

  3. modify.c — a function that changes its caller’s data. In A3 you wrote void swap(int a, int b) and it did not swap anything: the function got copies, and main’s variables never moved. Now write this:

    void double_all(int arr[], int n) {
        for (int i = 0; i < n; i++) {
            arr[i] = arr[i] * 2;
        }
    }

    And a main that makes an array, prints it, calls double_all, and prints it again:

    int nums[4] = {1, 2, 3, 4};
    print_all(nums, 4);
    double_all(nums, 4);
    print_all(nums, 4);

    Write void print_all(int arr[], int n) too — one line per element, or all on one line; your choice.

    In notes.txt, and this is the important one: swap could not change main’s two ints, and double_all can change main’s whole array. Both are functions, both take arguments, neither returns anything. In three or four sentences: what is different? Your honest guess is what is being asked for — the machinery that explains it is several weeks away, and nobody expects you to have it.

    Then one more line: double_all is given n separately. Why can it not just ask arr how long it is?

Part B — strings

  1. word.c — how long is a word, twice. Read one word from the user and print its length two ways: once with strlen, and once with a loop you write yourself that counts characters until it reaches '\0'. Print both. They must agree.

    char word[100];
    printf("word: ");
    scanf("%99s", word);

    Two things in that scanf to put in notes.txt:

    1. There is no &. Every scanf you have written so far said scanf("%d", &n). This one does not. Say what is different about word — one sentence, and a guess is fine.
    2. The 99 is a limit, and word holds 100. Try %s with no number and type in something much longer than a hundred characters. Say what happened. Then put the 99 back and leave it there.

    Your counting loop is three lines and it is the whole idea of a C string:

    int len = 0;
    while (word[len] != '\0') {
        len++;
    }
  2. shout.c — uppercase, in place. Write void shout(char s[]) which turns every lowercase letter in s into its uppercase version, changing the array itself rather than returning anything. main reads a word, calls shout, and prints it.

    It takes no length. It does not need one — the '\0' is the length.

    Do the conversion by hand rather than with a library call:

    if (s[i] >= 'a' && s[i] <= 'z') {
        s[i] = s[i] - 'a' + 'A';
    }

    That line only makes sense if a char is a number, which it is. In notes.txt: print 'a' and 'A' with %d instead of %c and say what the two numbers are, and what their difference is.

    There is a library function, toupper in <ctype.h>, that does exactly this. Now that you have written it, use it: replace your two lines with a call to toupper and check the program still works.

Part C — one program, three files

  1. Move the useful half into a library, and build it with make. Nothing new is computed here. This is A3’s Part B again, with this week’s functions, and it is deliberate repetition — it is the step most likely to turn up on Midterm Part 1.

    1. text.h — prototypes for double_all, print_all and shout, and nothing else. It needs its own include guard, named after this file and not NUMBERS_H:
    #ifndef TEXT_H
    #define TEXT_H
    
    void double_all(int arr[], int n);
    void print_all(int arr[], int n);
    void shout(char s[]);
    
    #endif
    1. text.c — the three definitions, copied from steps 3 and 5, and no main. Its first line is #include "text.h".

    2. main.c — main and nothing else, calling all three so the output makes it obvious which is which.

    3. Makefile — and this time write it from scratch rather than copying A3’s. It builds a program called text from main.c and text.c, and it has a clean rule.

    The whitespace before each command is a single tab, not spaces. With spaces, make says missing separator.

    Done when make builds it, ./text runs, and make a second time in a row says there is nothing to do.

    Then, in notes.txt: run make clean, then make, then touch text.h, then make again. Say what the last one rebuilt and why.

  2. Submit. All ten files on Brightspace. Compile on a lab machine one last time first.

Grading

Your programs are marked on compiling and running, and notes.txt on answering the questions in steps 1, 2, 3, 4, 5 and 6.

As in every assignment so far, written answers are graded on being honest about what you saw, not on being right. Step 2 in particular: “I predicted a crash, it printed 32767, and the number was the same all six times” is worth full marks. So is “I predicted a crash and I have no idea why it did not.” A confident description of something that did not happen is not.

A file that does not compile earns nothing for the parts that would have worked, so if you are out of time, hand in the version that builds rather than the version you were in the middle of.

Presentation

One of you will be asked on Thursday, September 24 to put your files on the projector and walk the class through them. You will not be told in advance. See the syllabus on how presentation is graded.

What else to do (optional part)

Ungraded. No points, and skipping them costs nothing.

  1. Make stats.c work for any number of values up to 100: read a count first, then that many numbers. What should it do if the user says 200?

  2. Write int contains(int arr[], int n, int target) returning 1 if target is in the array and 0 otherwise. Then write int index_of(...) returning the position, or -1 when it is not there. Why can it not return 0 for “not found”?

  3. Reverse a string in place, without making a second array.

  4. In offend.c, declare a second array int b[5] full of 77s beside the first, and then go hunting for it through a — print a[i] for i from -8 to 10 and find the 77s. They may be below a, at negative indices, rather than above it; which side they land on is not something the language promises. One array reading another array’s contents is among the two or three most consequential facts in computer security, and you now have the equipment to demonstrate it.

  5. Compile offend.c with gcc -Wall -O2 instead of gcc -Wall, and read what it says now. The compiler could see this the whole time. Why do you think it only mentions it when you ask for optimisation?

  6. Compile offend.c with gcc -Wall -fsanitize=address and run it. Instead of a number you get a diagnosis with a name. What is the name, and — given that this tool exists and is one flag away — why is it not simply on all the time?

  7. Work the Arrays and Strings set in Dive Into Systems: Exercises. They run in the browser and mark themselves.

  8. After the deadline has passed — and only then, per the AI policy — paste your step 3 answer into a chatbot and ask it whether you got the difference between swap and double_all right. Write one line on the part of its answer you could not follow. That part is week 9.