Lab 1: Compilation
This lab is designed to reinforce core systems themes by introducing you to the Linux command line and the basic C compilation process with Makefile automation.
- THEME: Information = bits + context
- THEME: Systems as foundational platforms
- THEME: Use the right tool for the job
To begin this lab, you will need a terminal window. If your local machine is Linux, you can simply open a terminal and begin. If you are using OS X or Windows, I recommend connecting to stu via an SSH client first. Doing this is beyond the scope of the lab.
1. Shell programs provide a command/response conversation
In this course, you will use a "shell" program as the primary method of communicating with the computer. A "shell" is a piece of systems software that interprets your commands. This is a fundamental computational paradigm. Even though you may be used to graphical interfaces, most computational interaction boils down to commands and responses (i.e., a conversation). Using a command line interface removes all distractions and complication, simplifying your "conversation" with the computer. The shell will run programs in response to your commands and show you the outputs of those programs.
TODO: Issue the simple command "date" by typing the command and pressing enter. Your shell will run the "date" program and display the results for you.
TODO: Try other commands: who, uname, uptime. What do they do?
2. Conversations have context (current file system location)
All conversations have some context; the bare minimum is the location where the conversation is taking place. Shells also have a notion of conversational context: the "working folder." This is your current location in the file system. It's like browsing in Finder on OS X or Explorer in Windows except you type commands to move around rather than clicking.
Linux has a standard tree-based folder structure, called the standard hierarchy. The hierarchy starts at the "root" (denoted simply by a slash, or "/"). Generally, you begin a shell session in your "home" directory. To find out where you are in relation to the overall file system, use the pwd (print working directory) command, and to see which files are in the working folder, use the ls (list) command.
TODO: Run "pwd"; where are you? Run "ls"; what files do you see?
Here are other commands that you can use to navigate and modify the files on the computer:
pwd "print working directory;" essentially "where am I?"
ls list the files in the current working directory
cd <dir> "change directory" to the given directory name
cd .. change to the parent of the current directory
cd change to your home directory
cat <file> "concatenate" a file; essentially "print this file to the screen"
cp <src> <dest> copy a file from "src" to "dest"
mv <src> <dest> move a file from "src" to "dest"; essentially "rename a file"
rm <file> remove a file
mkdir <dir> create a new directory
rmdir <dir> remove a directory (it must be empty first!)
TODO: Create a new folder called "cs261" and move into that folder.
There is an additional piece of context that helps the shell program interpret your commands: the PATH "environment variable" tells the shell where to look in the file system for the commands you type. Any command that you type without the full path must reside in one of the folders in your current PATH. You can look up the exact location of a command using the whereis command.
There are a couple of path shortcuts: "~" is your home directory, "." is the current working folder, and ".." is the parent folder.
Finally, if you forget how a command works, you can look up the manual for that command using the "man" command.
whereis <command> look up the location of a command (must be in your PATH)
man <command> read the "manual" page for the given command
TODO: Use the command "echo $PATH" to see your PATH variable. Use the command "whereis date" to see where the "date" command is.
3. C build process: source files -> preprocessor -> compiler -> linker -> executable
The C build process is described in Section 1.2 of your textbook. Basically, it is a series of steps that lead from source code files to an executable binary file (note that executables have no extension in Linux, whereas in Windows they are ".exe". In between are three main steps: preprocessing, compiling, and linking.
TODO: Create a new file called "hello.c" and enter the following C program (using a text editor--if you are unfamiliar with command-line text editors, start with "nano"):
#include <stdio.h> int main () { printf("Hello world!\n"); return 0; }
TODO: Compile the program with this command: "gcc -o hello hello.c" Run the resulting program with this command: "./hello" Why did you need the "./" part?
4. Command-line parameters change a program's behavior
It is standard to use command-line parameters or "switches" to change the behavior of a program. These switches consist of a dash and a letter (or multiple letters) after the command. To see which switches or options a command accepts, look up that command's manpage using the "man" command.
TODO: Try omitting the "-o hello" part from the gcc command; what does it do now? Try passing "-a" to the uname command--how does that change the output? Look at the manpage to see what other options you can pass in.
5. Header files (.h) declare interfaces, code files (.c) define implementations
One common practice in C that is a little different from Java is that we often separate the declaration of a function (its interface) from the definition of that function (its implementation; i.e., its code). In C, the declaration of a function is simply its return type, name, and function parameters followed by a semicolon.
TODO: Add the following definition to a new file called printer.c:
#include <stdio.h> void print_name () { printf("Hi, Duke!\n"); }
TODO: Add a call to "print_name()" in your main() function in hello.c and recompile; what happens?
Every source file (.c) or "module" is compiled separately. Interface or "header" files expose functionality in one module to other modules. The C language uses preprocessor directive to "include" one module's headers into another module.
TODO: Add the following declaration to a new file called printer.h:
void print_name ();
TODO: Add the following line near the top of your hello.c
#include "printer.h"
6. One code file = one module (includes header, compiles to one object)
To compile a single module, use the "-c" (compile only) switch.
TODO: Compile both modules.
7. Multiple objects (.o) link into one executable
Allowing multiple objects to link into one final executable file facilitates modularity and code re-use. To link multiple .o files, pass them all as command-line arguments to the gcc command.
TODO: Link both compiled object files into a single output executable called "hello_linked". Remember to use the "-o" switch! Run the resulting executable to make sure it works.
8. Builds can (and should) be automated
Changes to files require re-building parts of the project. Figuring out which parts need to be re-built can be tedious. Thankfully, programs called "build managers" can do this for us. The build manager we will use in this course is called Make. To use Make, you create a file called "Makefile" (note the capital "M" at the beginning), and then run the "make" command.
9. Makefile rules: dependencies -> build commands -> targets
Here is the general form of a makefile rule:
target: dependencies build command
WARNING: The build commands MUST be indented using a single tab character, not spaces!
To build a target, Make will check the modification times for all of its associated dependencies, recursively rebuilding them if necessary. Once that is complete, it will execute the provided build commands to rebuild the target.
TODO: Create a Makefile for your program, and test it by modifying one or more of the files and rebuilding.
10. The build process can be greatly customized
Read the latter sections of CS 240 Lab 1, beginning at the "Compiler Flags" section.