CS 70

Homework 2: Written Part

Before you do this part, decide whether you'll do it as a pair or individually. For the written portion of the assignment (only) you can choose.

Complete the written questions below. (They'll give you some additional practice with separate compilation.) Your answers are saved as you go, and you can come back and change them until the deadline.

Readings

Read one (or both!) of these contextual readings about the rich and intertwined history between textiles and computation.

  • Goat speaking

    I'm with my partner and we're ready to code. Can we do the reading later?

  • LHS Cow speaking

    Yes, you can skip this part for now and come back to it later, if that works better for you!

Question 1

Which reading(s) did you choose?

Question 2

What were the main takeaways from the reading you chose?

What in the world does sewing have to do with computer science, anyway?

Separate Compilation

In this exercise, you will get a chance to consider how compiling C++ code works when your programs get very large. We'll look at three different ways of organizing the code for two programs:

appreciate
Asks for a name from “standard input” (std::cin, usually the user keyboard) and then prints a well-known “2, 4, 6, 8, …” cheer for that name.
countdown
Simulates a rocket launch.

Both programs use a std::vector<int> (an object that simulates an indexed array of integers that can grow or shrink as needed, similar to an ArrayList in Java or a list in Python) and both programs call a helper function called printVector.

If you haven't already, clone the code repository for this assignment. In the separate-compilation directory, we have provided three different ways (badidea1, badidea2, and goodidea) to organize the code for appreciate and countdown. You will want to consult that code as you answer the following questions.

Bad Idea 1

For our first organizational attempt, we kept the two programs completely separate. Everything appreciate needs is included in the appreciate.cpp file and everything countdown needs is included in the file countdown.cpp.

For appreciate, we run

clang++ -c -std=c++20 -pedantic -Wall -Wextra appreciate.cpp
clang++ -o appreciate appreciate.o

which causes the compiler (clang++) to translate the human-readable code in appreciate.cpp into object code (appreciate.o) and then into an executable machine-language program called appreciate.

We build countdown the same way:

clang++ -c -std=c++20 -pedantic -Wall -Wextra countdown.cpp
clang++ -o countdown countdown.o

Question 3.1

If we want to change printVector() to always print a line break after each number in the vector, how many places in our C++ files do we need to make that fix?

Question 3.2

Once we've modified printVector() in our source code, which command(s) will we need to run to make sure both programs include our bug fix?

Question 3.3

Which functions would be recompiled (translated into object code again)?

Question 3.4

As a result, how many times will printVector() be compiled into object code?

Question 3.5

If we change countdown.cpp (e.g., to say “Lift Off!” instead of “Blast Off!”), which commands do we need to run to ensure that both executable programs are up-to-date?

Question 3.6

What functions would be recompiled (translated into object code again)?

Bad Idea 2

Having to copy-and-paste code between files by hand as we did in Bad Idea 1 should have set off alarm bells in your mind. Any time we find ourselves even thinking about copying and pasting code, we should look for a better way!

In Bad Idea 2, we put the code for printVector() into its own file, printvector.hpp. Then we use #include to instruct clang++ to automatically copy-and-paste the contents of printvector.hpp into countdown.cpp and appreciate.cpp at the point where the #include lines appear.

Our two programs can be compiled with the same commands we used in Bad Idea 1:

clang++ -c -std=c++20 -pedantic -Wall -Wextra appreciate.cpp
clang++ -o appreciate appreciate.o

for appreciate, and

clang++ -c -std=c++20 -pedantic -Wall -Wextra countdown.cpp
clang++ -o countdown countdown.o

for countdown.

Question 4.1

If we want to change printVector() to always print a line break after each number in the vector like we did in Bad Idea 1, how many places in our C++ files do we need to make that fix?

Question 4.2

After we've made the changes to our source code, what command(s) will we need to run to make sure that both programs include our bug fix?

Question 4.3

As a result, how many times would printVector() be compiled into object code?

Question 4.4

If we change countdown.cpp (e.g., to say “Lift Off!” instead of “Blast Off!”), which commands do we need to run to ensure that both executable programs are up-to-date?

Question 4.5

As a result, what functions would be recompiled (translated into object code again)?

  • Goat speaking

    Meh. We only have to edit one file now, but we still have to do just as much work to compile everything!

  • LHS Cow speaking

    True. Let's try something else.

Good Idea

While Bad Idea 2 has some advantages over Bad Idea 1 (at least we aren't still copying and pasting code by hand!), it's still not taking full advantage of the modularity of our code. Once we've translated the code for printvector.hpp into machine instructions once, it's silly to compile it again if its source code hasn't changed.

Our third attempt splits our code into four different files:

printvector.hpp
Includes the declaration of the printVector() function; a promise to the compiler that machine code for a printVector() function will exist eventually.
printvector.cpp
Gives the definition of printVector().
countdown.cpp and appreciate.cpp
Are no longer completely separate pieces of code but rely on the promise (expressed in the printvector.hpp file they both now #include) that machine code for printVector() will be defined elsewhere.

Now we need five commands to build our programs,

clang++ -c -std=c++20 -pedantic -Wall -Wextra printvector.cpp

creates printvector.o from the code in printvector.cpp. Now we can build appreciate with

clang++ -c -std=c++20 -pedantic -Wall -Wextra appreciate.cpp
clang++ -o appreciate appreciate.o printvector.o

The first line creates an object file from the code in appreciate.cpp (and printvector.hpp, which is #included in appreciate.cpp). It's the same command we used in our previous examples.

But the second line is different:

clang++ -o appreciate appreciate.o printvector.o

We've added printvector.o to our command, so clang++ links both appreciate.o and printvector.o to create a complete executable file called appreciate.

We do the same thing with countdown:

clang++ -c -std=c++20 -pedantic -Wall -Wextra countdown.cpp
clang++ -o countdown countdown.o printvector.o

In summary, appreciate.cpp and countdown.cpp are first translated to machine instructions (.o files) based on just the declaration (interface specification) of the printVector() function. Then, to form executables, appreciate.o and countdown.o are each linked with the definition of printVector(), whose machine instructions are in printvector.o.

Question 5.1

If we want to change printVector() to always print a line break after each number in the vector, how many places in our C++ files do we need to make that fix?

Question 5.2

After we changed printVector() in our source code, what command(s) will we need to run to make sure that both programs include our bug fix?

Question 5.3

As a result, how many times is printVector() compiled into object code?

Question 5.4

If we now change countdown.cpp (e.g., to say “Lift Off!” instead of “Blast Off!”), which commands do we need to run to ensure that both executable programs are up-to-date?

Question 5.5

What functions would be recompiled (translated into object code again)?

Good Idea is the way that C++ programmers use separate compilation in practice:

  • Put function definitions in source files (.cpp).
  • Put function declarations in header files (.hpp).
  • Only ever #include header files, and only when the declarations are needed.
  • Only ever compile source files (.cpp).
  • When changes are made, recompile only the necessary source files and then relink the executable(s).

The advantages of this method (both in reducing code duplication and having to recompile less code) are even more apparent in “real world” C++ programs of nontrivial size, where compilation might take many minutes or even hours.

Unfortunately, while we've made it possible to spend less time compiling code, an obvious disadvantage of this approach is that it becomes harder to figure out which commands you need to run to rebuild everything you need to get your final executable program when you've changed a particular source-code file.

  • Goat speaking

    So now we have even more things to keep track of? I think I'll just keep all my code in one file.

  • LHS Cow speaking

    We've added some complexity, yes, but we're computer scientists—we love having opportunities to simplify complex situations!

Question 5.6

Would it be helpful to augment the Good Idea strategy with a utility program that automatically figures out which .cpp files to recompile and which .o files to link, whenever the program changes?

To Complete This Part of the Assignment…

You'll know you're done with this part of the assignment when you've done all of the following:

(When logged in, completion status appears here.)