Goals
- Introduction to the Environment we will use in CIS 3990: The UNIX Terminal via bash.
- Introduction to C++ code and refresher on some aspects of C
- including libraries
- pointers as arrays
- C-style strings as null-terminated arrays of strings
- Header-guards & Makefiles
- command line argument parsing
Collaboration
For assignments in CIS 3990, you will complete each of them on your own. However, you may discuss high-level ideas with other students, but any viewing, sharing, copying, or dictating of code is forbidden. If you are worried about whether something violates academic integrity, please post on Ed or contact the instructor(s).
Setup
Task 1. Setup your environment to gain access to a development environment for the course.
If you haven’t already, you need to follow the Environment Setup. We recommend you try and figure this out ASAP. This includes setting up a linux environment, setting up and cloning your course github repository, and choosing a text editor to code with.
Once you have your environment setup, that is all that is a necessity, however we will give some suggestions on how to use the environment here:
Refresher on the Terminal and Shell
One of the goals of this course is to continue developing your experience with the shell. We expect that you have used the shell some before in CIS 2400, but we know you may not remember it all, and there is still more to learn than what is covered in that course. If you took CIS 2400 w/ Travis then this section may be familiar.
Most people learn the basics of a shell in school and slowly learn more as needed, but if you want to learn more we recommend two things:
- There is a mini course occasionally taught here under the name CIS 1911: Using and Understanding Unix and Linux
- Chapter 17.1 of Dive into Systems: Using Unix
Most computers have some sort of a “Terminal” application that runs a program called a “shell”. The “shell” is a program that prompts the user for a command, runs the command, prints any output and then repeats by asking the user for another command. This makes a shell effectively a REPL (Read Eval Print Loop), but it also supports a lot more features.
A shell’s main job is to provide an interface for users to the system they are interacting with. For us this primarily involves:
- Managing and navigating the filesystem (organizing files, directories, etc.)
- Running existing programs on the system (ones we write, annd already existing ones like the compiler)
Shell programs are somewhat dependent on the system they are implemented for, but this means shell programs for the same system (e.g. bash, zsh, fish, etc.) have a lot in common.
This course uses the shell bash and many of what you learn in bash can be applied to shells used in other UNIX-like systems (UNIX-like systems are pretty much everything that is not Windows).
bash is one of the most popular shells in existence and getting familiar with it (and thus other UNIX-shells) is very beneifical for most who do any programming.
When you open the shell, you should see something like this:
moya@4d98e91a6924:~/shared$

This is the prompt that gets printed to let you know you can type in a command for the shell to execute.
There are a couple parts to this line, lets break this down.
-
moya: Before the@identifies the username of the current user (you). You are probably logged into the docker container asmoya -
4d98e91a6924: This part is after the@and before the:. This part also probably looks different for you and is the “name” for the system the shell is runninng in. -
~/shared: This part that comes after the:identifies the current directory you are in.~is the “home” directory that is the home of a user. In this example we are in theshareddirectory and the/indicates thatsharedis within the~directory.
Note: Things may look a little bit different depending on what you are using (Docker Desktop, WSL, Orbstack, etc.). Notably your path should still contain ~ in it even if the shared part may be msising.
The last bullet point from above is particularly important. The shell has a “Current Directory” similar to how File Explorer or Finder has a notion of “Current Directory” where those programs need to go in and out of directories to see different files. What this means is that when you are interacting with the shell, there is always a directory you are “in” and you can change what directory you are in to interact with different files.
Setting up our files
Since you have cloned your git repository already, some of the files have already been setup for us!
From here we can use another command ls which “Lists” the contents of the current directory. If you cloned your repo correctly you should see a directory with the same name as your repo.
After running the ls command your terminal should look something like:
moya@4d98e91a6924:~/shared$ ls
25fa-cis3990-xyz
moya@4d98e91a6924:~/shared$
where xyz is instead your github username.
The ls command only gives us information and does not change anything in the system, but is one of the most commonly used commands. It helps to do run ls to remind you of what else is in the directory you are currently working in.
from here we can use the command cd to “Change Directory” to move our current directory into the repo’s directory:
cd 25fa-cis3990-xyz
Note that after you do this, your shell prompt will look different. It will probably look something like:
moya@4d98e91a6924:~/shared/25fa-cis3990-xyz$
Note that the path in the prompt changed to include the directory we are now in. If we now create any files, they will show up in this directory.
if we type in ls again then we should be able to see two directories:
moya@4d98e91a6924:~/shared/25fa-cis3990-xyz$ ls
check_setup cowsay
We need to change into our cowsay directory as that is the current project. Afterwards our shell prompt should look something like this:
moya@4d98e91a6924:~/shared/25fa-cis3990-xyz/cowsay$
If you ls from within here you should notice all of the files we provide:
moya@4d98e91a6924:~/shared/25fa-cis3990-xyz/cowsay$ ls
Makefile catch.cpp catch.hpp SimpleString.hpp test_simple_string.cpp test_suite.cpp
For this assignment, we will need to create a new file cowsay.cpp.
To create a file in the shell we can run the following command:
touch cowsay.cpp
touch creates a file of the specified name if it does not already exist. Note that it does not create directories, that is what mkdir is for.
From here we can use ls to see that you have created the file with the touch command. If you did everything properly, you should see something similar to the below. If something went wrong, see further below where we talk about the mv command.
moya@4d98e91a6924:~/shared/25fa-cis3990-xyz/cowsay$ ls
Makefile catch.cpp catch.hpp cowsay.cpp SimpleString.hpp test_simple_string.cpp test_suite.cpp
Once you have done the above steps to create our cowsay.cpp you must run the following commands to download the starter files for the project.
From here you can start working the assignment by opening the files we just created with vim, VSCode or another editor if you have one you prefer.
More Basic Shell Commands (Optional)
If you want to rename a file (because you accidentally spelt it wrong) or you want to move it to another directory, you can use the mv command which stands for ““Move”.
For example, if I wanted to rename the file example.txt to renamed.txt (assuming that example.txt already exists) then I could type:
mv example.txt renamed.txt
Similar to the mv command there is the cp command for “Copying” a file. If I wanted to make a copy of a file I would do:
If I wanted to rename example.txt to copied_example.txt, I would do:
cp example.txt copied_example.txt
Lastly, we should let you know about . and .. (referred to as “dot” and “dot dot” in lecture). These two are “special” names that change based off of which directoy you are currently in.
. (dot) refers to the current directory, so the following two names refer to the same file: ./hello.txt and hello.txt.
.. (dot dot) refers to the directory that is “above” or the “parent” of the current directory (The current directory is held within its “parent” directory). This can be used to leave a directory that we have entered. For example, if i have a directory called: intro, I can run: cd intro to enter that directory and later type cd .. to leave it and go “up” a level in the directory layout.
For another example, lets say I am in a directory called “example” that has the file blah.txt. I can refer to that file with ../example/blah.txt ./blah.txt or blah.txt and all three would be the same.
More information on the shell that is likely to be useful:
Lastly, we have a quick reference on some information you may find useful when using the terminal. Feel free to come back to it at any time: bash-reference
Instructions
Once you have followed the setup instructions, you should have a folder that contains the files for this assignment.
Note: you are restricted from using most of the standard C and C++ library in this assignment. See the section below on allowed functions for more.
Required Knowledge
This homework has you writing a little bit of C++ code from scratch. For now this should be very similiar to code you have written before in C, but with only a few minor differences. The differences you need to know should have been covered in the first lecture or two. These being:
- instead of using
malloc()we use thenewkeyword - instead of using
free()we usedeleteordelete [] -
constcan be used to mark a variable as constant (cannot be modified) - printing in C++ with
coutorcerrand<< -
EXIT_SUCCESSandEXIT_FAILUREexit codes from<cstdlib>
C++ (like C) doesn’t provide variables with a default initial value. When you declare a new variable be sure that you assign it a value.
For this assignment, you will be writing some code to implement a string struct (which it turns out isn’t that far from being a proper C++ object) and slightly simplified version of the shell command cowsay, but in C++.
SimpleString
For the first part of the assignment you will have to implement a simpel string “object” in C++.
An important goal of this class is gaining a better understanding of how the computer works, and part of that is trying to build a better understanding for how fundamental types (e.g. string’s) work! This also should be on the lighter side for workload :)
This “object” still follows a lot of C style conventions, so you should not follow everything done in this section as “good practice”. This assignment is designed this way to help people refresh on C and transition into C++. Knowing the fundamentals of C is important for understanding C++, but don’t worry if it takes some time to figure this out!
For this part of the assignment we suggest you start by opening SimpleString.hpp and reading the code and comments.
Once you have familiarized yourself with what is going on, you should open the empty SimpleString.cpp you created earlier in the assignment and provide the implementation for every function descrived in the SimpleString.hpp header files. Your cpp file should #include the corresponding hpp file. If you need advice for how to layout your file, you should check some of the provided lecture code.
cowsay
Your second task is to write a C++ program that acts like the shell command cowsay!
We aren’t going to give you too many details on what cowsay does, this is something we want you to figure out!
You may need to run the following command to install cowsay
sudo apt install cowsay
If it asks you for a password, try typing in systemsRcool. It may not show the password as you type it, that is normal.
You can then run the command to see what it does! Try running:
cowsay hello how are you?
If for some reason it is not working for you. instead of cowsay try running /usr/games/cowsay
Your job is to write your cowsay.cpp to replicate some of the behaviour of the cowsay command.
To make your life a little easier, you can work with the following assumptions:
- There should always be at least one command line argument (other than the program name). If there is less than that, you should print an error to
cerrand returnEXIT_FAILUREfrom main. - All of the command line args are a single word. There is no quotation marks in the input so that multiple words are passed as one arg. e.g.
cowsay "this is one argument with seven words". - You can assume that no word passed via command line argument is 40 or more characters in length
- All command line args (other than the program name) are just words for your cow to say. You don’t have to handle any other shell “options” or non-ascii characters.
In lecture we showcased how to use command line args if you need a refresh on that.
You are not required to make use of your SimpleString for this part of the assignment. You can use it if you like, though some find it a little clunky, especially since it isn’t quite a proper “string object” yet.
If you want to use it, you should check how we include header files in some of the provided lecture code and do something similar for SimpleString.hpp.
You will also need to modify the makefile to test your code. See the compilation section below for how to do this.
Your code should compile to an executable called cowsay so that you can run it like: ./cowsay.
Running the command ./cowsay hello how are you should do the same as running the shell command cowsay hello how are you.
Other requirements:
- Your program should compile without warning or error. You should follow what we have in the section below on compilation.
- It should not crash or have any memory errors. If you encounter an error, your code should return from main or exit with status code
EXIT_FAILURE - Your code should print out exactly as the format of
cowsay - Your code should use the
SimpleStringyou made in the first part of this assignment. - Your code must be robust. Be sure you handle the wrong number of command line args and bogus input well. What we mean by this is that your code should not crash, have any memory errors and handle things “gracefully”.
printing in C++
You will need to do some printing in this code, and since we are writing C++, you should print like we do in C++. We may not have talked about it in lecture, but to print a string in C++ we just do:
// prints "VALID" to the terminal
cout << "VALID" << endl;
You can also print numbers and characters this way if it would help with debugging:
char c = 'A';
// prints "A" to the terminal
cout << c << endl;
We also provided a function for you so that you can print SimpleString “objects”
SimpleString str = from_cstring("Hello!");
// prints "Hello!" to the terminal
cout << str << endl;
Just add this code to your SimpleString.cpp file:
std::ostream& operator<<(std::ostream& os, const SimpleString to_print) {
os << to_print.data;
return os;
}
Allowed / suggested functions & headers
For this assingment you are allowed to write any helper functions you need, but you are restricted to using the following headers and the following functions. You do not need to use all of these, do what you think would be best for your code. If you do not see a function listed that you think should be ok to use, please ask and we can allow it or disallow it.
- cstdlib
exitEXIT_SUCCESSEXIT_FAILURE
- iostream
coutcerrendl
You are also allowed to use SimpleString and any functions you write yourselves.
Compilation
Header Files and Header Guards
For this assignment we gave you SimpleString.hpp, which is a header file.
Header files contain the declarations of types and declarations of functions that are defined in the corresponding cpp file.
If some other file wanted to use our SimpleString code, they would need to “import” our code by doing #include "SimpleString.hpp" to see what functions and types there are.
Later in the compilation process is when all the .cpp files are combined and can see the definitions for everything.
However how “importing” modules in C++ works is rather outdated and the proper C++ “import” statements are still being developed.
This means we need to add something to our .hpp files to make sure they are not “included” multiple times as this will cause compilation issues.
This thing we add is called header guards. How they work is that at the top of your hpp file you need to do something like:
#ifndef MY_FILE_HPP_
#define MY_FILE_HPP_
then at the bottom of the hpp file you would write something like:
#endif // MY_FILE_HPP_
The above examples are what you would write for MyFile.hpp or my_file.hpp.
You can see an example of this in some of the lecture code.
For your code to get full credit, you will need to add header guards like this to your SimpleString.hpp.
If you have coded in C or C++ before you may be tempted to use #pragma once instead of header guards. While this is relatively common, it is not actually standard in C++ and can differ in behaviour in different C++ compilers (or may not be supported by a compiler at all!). Thus we are not going to use #pragma once in this class)
Makefile
Makefiles is something you should have learned from a previous course (CIS 2400) but just in case, there is a short crash course on Makefiles here. It is ok if you don’t remember Makefiles. They are very easy. They just look scarier than they actually are.
You must also modify the provided Makefile to compile SimpleString.o and compile your cowsay.cpp into a program called cowsay.
We provide a lof of the makefile for you, and you will also note that the makefile includes steps for building test_simple_string.o, catch.o and test_suite.o. It then uses these .o files to create an executable called test_suite.
You should use what we do in the Makefile with building catch.cpp into catch.o and using it to make SimpleString.o and cowsay.o.
You should also then compile your cowsay.o into an executable called cowsay (similar to what we did for compiling test_suite from test_suite.o).
This is not as hard as it may sound! It may look scary, but you can do it!
Here is everything you need to add to the makefile. Some of it is repeated, but this should help:
- add a rule to compile
SimpleString.ofromSimpleString.cppandSimpleString.hpp - add a rule to compile
cowsay.ofromcowsay.cpp - add a rule to compile the
cowsayexecutable fromcowsay.o(and optionallySimpleString.oif you use that in cowsay) - modify the
test_suitecommand to includetest_simple_string.oin the build command. - modify the
test_suiterule to also useSimpleString.o - modify the
cleancommand so that it also cleans up thecowsayexecutable file - The new rules you add should use the same compiler and most of the same compiler flags as other rules.
- compile with extra information for a debugger and the dwarf 4 debugging rule
- have the “enable all warnings” option turned on
- use C++ version
c++2b
Testing your code
Valgrind
You need to test your submission on whether there are any memory errors or memory leaks. We will be using valgrind to do this. To do this, you should try running:
valgrind --leak-check=full ./cowsay I am a cow MOOOOOOOOOOOOOOOOO
You should also test your string code by running: valgrind --leak-check=full ./test_suite
If everything is correct, you should see the following towards the bottom of the output:
==1620== All heap blocks were freed -- no leaks are possible
==1620==
==1620== For counts of detected and suppressed errors, rerun with: -v
==1620== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
If you do not see something similar to the above in your output, valgrind will have printed out details about where the errors and memory leaks occurred. We went over how to read valgrind errors some time in recitation or lecture in the first or second week.
Note that the last part of the valgrind command we tpyed in for cowsay is just what we would input to run our program normally. You should try running your program on various inputs. (e.g. some valid and some invalid) to make sure it passes for all test cases.
Testing
Testing your SimpleString with catch2
As with mentioned above, you can compile the your implementation by using the make command. This will result in several output files, including an executable called test_suite that we are using to test your SimpleString implementation.
After compiling your solution with make, You can run all of the tests for the homwork by invoking:
$ ./test_suite
You can also run only specific tests by passing command line arguments into test_suite
For example, to only run the tests on the core operations of the hash table (inserting, finding, and removing) you can type in:
$ ./test_suite from_cstring
You can specify which tests are run for any of the tests in the assingment. You just need to know the names of the tests, and you can do this by running:
$ ./test_suite --list-tests
These settings can be helpful for debugging specific parts of the assignment, especially since test_suite can be run with these settings through valgrind and gdb!
Testing your cowsay
Your code should compile to an executable called cowsay so that you can run it like: ./cowsay.
To test your cowsay locally, all you need to do is compare it’s output to the real cowsay program, as long as the command line args follow the assumptions we setup for you (e.g. no word that is 40 characters or more).
Running the command ./cowsay hello how are you should do the same as running the shell command cowsay hello how are you.
Test your cowsay on various other inputs and try to match the real cowsay!
Clang Format and Clang Tidy
The makefile we provided with this assignment is configured to help make sure your code is properly formatted and follows good (modern) C++ coding conventions.
To do this, we make use of two tools: clang-format and clang-tidy
To make sure your code identation is nice, we have clang-format. All you need to do to use this utility, is to run make format, which will run the tool and indent your code propely.
Code that is turned in is expected to follow the specified style, code that is ill-formated must be fixed and re-submitted.
clang-tidy is a more complicated tool. Part of it is checking for style and readability issues but that is not all it does. Examples of readability issues include:
Not using curly braces around if statements and loops:
if (condition) // clang-tidy will complain about missing curly braces
cout << "hello!" << endl;
Declaring variables or parmaters with names that are too short:
void foo(char c) { // clang-tidy will complain about the name `c`
// does something
}
Having functions that are too complex and long. The tool calculates “cognitive complexity” of your code and will complain about anything that is too complex.
This means you should think about how to break your code into helpers, because if you don’t, clang-tidy will complain and you will face a deduction.
More on this specific error can be found here: Cognitive Complexity
clang-tidy is also useful for noticing some memory errors and pointing out bad practices when writing C++ code.
Because of all this, we are enforcing that your code does not produce any clang-tidy errors. You can run clang-tidy on your code by running: make tidy-check.
Whenever you compile your code using make then it should also re-run clang-tidy to check your code for errors.
Note that you will have to fix any compiler errors before clang-tidy will run (and be useful).
Code that has clang-format, clang-tidy or any compiler errors will face a deduction.
If you have any questions about understanding an error, please ask on Ed discussion and we will happily assist you.
Submission
Please submit all of your completed files Makefile, SimpleString.cpp SimpleString.hpp and cowsay.cpp to Gradescope. Note that we expect you to submit through your github repository, and we expect your files to be within the cowsay directory of your github repository.