The C Development Environment

During this course we will be working in a relatively standard development environment for C programming. In CSC 151 you likely used DrRacket or Scamper; these programs contain (almost) everything you used to write, run, and test programs. With C, we use separate tools to complete different tasks. This is partly a product of C’s age—the language dates back to the 1970s—but it also reflects a philosophical difference in system design between Lisp-like languages (e.g. Racket and Scamper) and low-level languages like C. There are advantages and disadvantages to both approaches, but our goal with this course is to work in an environment that is close to what you would find if you worked as a professional software developer.

Each section below describes a task we’ll need to do and the specific tools we’ll use in this course. Some sections include example commands that you can run in a command-line interface, which is also often referred to as the “terminal” or “shell.” You might not have used a command line interface before, so don’t worry if this is unfamiliar; we’ll practice this in our first lab.

Editing Code

We need some sort of program that allows us to write C programs and save them to source files. For this class, we will use Visual Studio Code (VSCode for short). VSCode is an example of an integrated development environment (IDE), meaning it can support some of the tasks below as long as we configure it correctly. We’ll mostly be using VSCode strictly as an editor, but some of its IDE-related features are convenient.

One nice feature of VSCode is that you can launch it from the command line. If you have code in the directory csc161/labs/week1, you can run this command to start VSCode in that directory:

$ code csc161/labs/week1

Note that the $ character is displayed by the command line interface, not something you should type in. You’ll practice running commands like this and using VSCode in our first lab.

Compiling Code

C is a compiled language, which means we need to translate the code we write in source files into a runnable program, or an executable. The compiler is the tool that does this for us. For this class we will be using the clang compiler, although you will see references to another compiler called gcc in readings and other resources. These compilers are largely interchangeable, although clang generally gives clearer error messages and warnings if you ask it to compile code that contains errors or unsafe operations.

If you have a source file named hello.c, you can compile it with clang using this command:

$ clang -o hello hello.c

We’ll look closer at the options we pass to clang in our first lab.

Building Large Programs

Different programs will require different options and steps when we compile them, which means it can be cumbersome to run the compiler each time we edit our code. For example, larger programs will often include many different source files that must be compiled and linked together to produce an executable. Compiled languages generally rely on a build system to manage this process. The canonical build system for C programs is make, which dates back to the earliest days of C.

Instead of running the clang command in the previous section, you could instead run the build using make like this:

$ make
clang -o hello hello.c

Note that the line below $ make is output from the make build, not something you’ll type in. This example assumes you have a file named Makefile that tells make how to run the build. We will provide Makefiles for your labs and assignments until you learn how to write them yourself later in the semester.

Running Programs

Once you’ve written a program in a source file and compiled it to an executable you can run the program. This is what happens when you double-click (or tap) an application on your preferred computing platform, but for C programs we generally launch them from the command line. A command-line interface is a text-only way to interact with a computer. Much of the command line interface you’ll see in this course is inherited from the era before graphical operating systems, but the command line remains a powerful and flexible way to interact with modern computers.

Assuming you’ve created a program called hello by running make, you can run that program like this:

$ ./hello
Hello, world!

Again, the line that does not begin with $ is output, not something you’ll type yourself. We will practice running this and other programs on the command line in our first lab.

Finding and Fixing Bugs

As with any kind of problem solving, we’ll often write programs in C that don’t work correctly the first time around. There are several tools that can help us test C programs and understand why they behave the way they do. We will use the GNU debugger (gdb) and AddressSanitizer to test and examine our programs. We will learn about these tools a few weeks into the semester; until then, you’ll have to rely on some ingenuity and careful reading to understand where your programs may be going wrong.

Sharing and Collaborating with Others

Software development is a collaborative process and, like all the tasks above, we have tools that help us with this collaboration. We will be using a version control system called git to track and share changes to the programs we write in this class. Along with the git tool, we’ll be using a web-based platform called Gitea to store and manage access to our code. Gitea is similar to GitHub, a common platform for sharing and collaborating on software projects. Unlike GitHub, our Gitea instance is be hosted locally and will only share your work with course staff and your group members during the semester.

We’ll use git for nearly all the work we do this semester. Before you start the lab it is important that you understand some basic concepts in git.

What is a Version Control System?

Git is a version control system, but what does that even mean? At the most basic level, a version control system allows us to track many different versions of our code. In git and many other version control systems we have the notion of a repository and a series of commits. Each project managed with a version control system will have a repository. Within a repository, we track the series of edits made to different files in the project using commits.

One of the most basic benefits of a version control system like git is that you can quickly roll changes back to an earlier version—git can work like a powerful undo button. Imagine you are working on a project and making changes to your code. You’ll make a series of edits to add some functionality, then commit those changes to the repository. After committing your work, you can start to make more changes to your code. If you are happy with the changes you can make another commit. But, if you decide you are unhappy with the changes you made, you can also roll the repository back to the previous commit, essentially jumping back to the last working version of the code. We can also jump back to earlier commits in git, so if you make a series of commits to your project and then change your mind you can jump back to any earlier version of the project.

If you want git to work well as a big undo button, you’ll need to make an effort to save your work through commits. This gives us our first important tip for using git.

Tip 1 Commit Frequently: Whenever you have finished making a set of changes to your project that you want to save, commit them.

But as you make commits, you’ll want to save versions of your work that you would actually want to roll back to. You’ll be very frustrated if you try to undo a series of changes and you’re left with a broken project. That gives us our second tip:

Tip 2 Commit Working Changes: When you commit changes, always make sure your project is in a working state.

Any time you make a commit, git will ask you to write a short message to describe the commit. These messages should be brief but descriptive. You may find yourself looking back through old commits to look for a particular change. That will be hard if your commit messages aren’t clear, so make sure you follow our next tip:

Tip 3 Write Descriptive Commit Messages: Commit messages should clearly but concisely explain what you changed and why.

One important reason to commit changes frequently is to make sure you can actually describe what you’ve done. If you find yourself unable to write a concise commit message then you might not have been following tip 1.

Collaborating with Git

One of the characteristics that makes git special is that it is a distributed version control system. That means we allow different copies of a repository to exist and interact. You can make a copy of a repository, commit changes, and then send those changes back to the original repository. But the original copy of the repository isn’t special; your lab partner could make a copy of your copy, commit changes, and then send them to you. This flexibility is useful for large projects with many dvelopers, but it can also make things quite complicated. We’ll be using git in a more limited way to keep things simple for now.

All of the git repositories you work with in this class will reside on our Gitea server. In order to work on a repository you will need to clone the repository. When you clone a repository, git makes a copy of all the commits in that repository. Your cloned copy of the repository will reflect the state after the latest commit to the repository, but you can roll back in time or add commits in your own copy. But as long as you are working in your own copy of the repository, only you will be able to see your commits. You’ll want to save those commits by pushing them back to the repository you cloned from, which git calls the “origin”. It is a good idea to push commits when you are finished working for a while, or when you’ve made a series of commits that someone else working on the project should be able to build upon. That gives us our next tip:

Tip 4 Push when you Finish Working: Push the commits from your cloned repository to the origin any time you stop work, or even more frequently than that.

One consequence of git’s distributed design is that different developers can work on separate copies of a repository. While you are committing changes to your copy of the repository, another developer may be committing changes to their own separate copy. If the other developer pushes commits to the origin repository you can get those changes by pulling them from the origin. Things can get complicated when you work with other developers. If you both edit the same file(s), you’re going to have to do some extra work to combine those changes. Merging changes from two different developers is common in large projects, but it is best to avoid this work if you can by following our next tip:

Tip 5 Pull when you Start Working: Always pull changes from the origin before you start to make sure you are working from the latest version of the project.

If you pull changes from another developer that conflict with your own changes you’ll have to merge them. Sometimes this can happen automatically, but you may need to look at a file that contains edits from multiple commits and decide how to combine them. Luckily, VSCode does a good job formatting these changes so you can decide which edits to keep. If you find yourself in this situation you may need to ask course staff for help; git can be a little cumbersome and it doesn’t always give you especially helpful messages. The best way to avoid issues with merging is to diligently pull when you start work, commit frequently, and push all your commits when you step away. You’ll be working with your partner(s) sitting next to you when you complete labs so you should be able to avoid merge conflicts entirely.

A Simple Git Workflow

To sum up the guidance above, here is quick run-through of a simple git workflow:

  1. Create a repository on Gitea (the instructor will usually do this for you).
  2. Use the git clone command to make a copy of the repository on your computer.
  3. Edit the files in the repository to make progress on your assigned work.
  4. Commit those changes to the repository with the git commit command.
  5. Repeat steps 3 and 4 as many times as needed.
  6. Push all your committed changes when you finish working for a while using the git push command. You may also choose to push commits in the middle of a work session.
  7. When you come back to work working, run the git pull command to make sure you have the latest version of the project.
  8. Repeat steps 3–6 until you’ve finished the project.

This workflow should cover 99% of your git use in this class, but you’ll sometimes need to deviate especially if you forget to pull, commit, or push changes at some point. We’ll discuss this basic workflow in class, and you’ll have many opportunities to practice using git throughout the semester.