Goals
- Introduction to the Environment we will use in CIT 5950: UNIX Terminal and Docker.
- 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 CIT 5950, 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 docker to gain access to a development environment for the course.
If you haven’t already, you need to follow the Docker Setup. We recommend you try and figure this out ASAP.
Once you have the docker container setup, that is all that is a necessity, however we will give some suggestions on how to use the environment here:
Intro to the Terminal and Shell
One of the goals of this course is to get you experience with the shell. We will cover some basics in class and here, but there is simply too much to talk about in the course. 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:
root@dbd8f45192c1:~/workspace#
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.
-
root
: Before the@
identifies the username of the current user (you). You are probably logged into the docker container asroot
-
dbd8f45192c1
: 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. -
~/workspace
: 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 theworkspace
directory and the/
indicates thatworkspace
is within the~
directory.
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.
With this information, we are able to start using the shell to create our files for the homework.
Setting up our files
We recommend you organize your work into separate folders. Suggested names for this assignment’s folder are intro
, setup
or hw00
.
To create a folder (also called a directory) you should just be able to type the following command into the terminal:
mkdir intro
mkdir
is a command to “make a directory” and in the example above we are using it to create a directory called “intro”.
from here we can use the command cd
to “Change Directory” to move our current directory into the newly created one:
cd intro
Note that after you do this, your shell prompt will look different. It will probably look something like:
root@dbd8f45192c1:~/workspace/intro#
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.
For this assignment, we will need to create a new file simple_string.cpp
.
To create a file in the shell we can run the following command:
touch simple_string.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 another command ls
which “Lists” the contents of the current directory.
ls
You should see the files that you have created with the touch
command. If you did everything properly, you should see something similar to the image below. If something went wrong, see further below where we talk about the mv
command.
This 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.
Once you have done the above steps to create the directory and our simple_string.cpp
you must run the following commands to download the starter files for the project.
curl -o simple_string.zip https://www.seas.upenn.edu/~cit5950/current/projects/code/simple_string.zip
and
unzip simple_string.zip
after this you can ls
again and see that we have the file check-time.cpp
, Makefile
, simple_string.hpp
, ctypes.hpp
and ctypes.cpp
added to our local directory.
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++ 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 thenew
keyword - instead of using
free()
we usedelete
ordelete []
-
const
can be used to mark a variable as constant (cannot be modified) - printing in C++ with
cout
and<<
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 “object” in C++ and a program that takes in a single command line argument (other than the program name) that represents a time and then prints whether or not that it is in a valid format.
simple_string
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 simple_string.hpp
and reading the code and comments.
Once you have familiarized yourself with what is going on, you should open the empty simple_string.cpp
you created earlier in the assignment and provide the implementation for every function descrived in the simple_string.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 code: ctypes.hpp
and ctypes.cpp
!
check-time
Your second task is to write a C++ program that checks whether or not the input argument represents a valid time.
Your code should compile to a program called check-time
. We have also provided an example of how a user would run your program and the expected output of the program (you should match this output):
root@dbd8f45192c1:~/workspace/intro# ./check-time 11:59
VALID
root@dbd8f45192c1:~/workspace/intro# ./check-time 5:16
VALID
root@dbd8f45192c1:~/workspace/intro# ./check-time 2:70
INVALID
root@dbd8f45192c1:~/workspace/intro# ./check-time 31:20
INVALID
root@dbd8f45192c1:~/workspace/intro#
We have given you the start of the program in check-time.cpp
. This is where you should write your code.
We already provided how to get the string to check from command line arguments, but it is up to you to check the validity of the string and output the correct value.
A valid string consists of 1 or 2 digits followed by a colon :
and two more digits.
The first group of digits together can represent any number from 0
to 23
(inclusive). Note that a leading zero is ok, so something like 00
is fine for the first set of digits.
Following the first 1 or 2 digits is a :
.
After the colon is a set of two digits that can range from 00 to 59 (inclusive). Note that there will always be two digits for this last group.
Here are a few examples of valid times:
19:59
6:03
23:07
17:32
00:00
0:00
Here are a few examples of invalid times:
-
52:32
(since the first digit is a 5. “52” is not a valid first part of the time) -
10:5
(there must always be two digits at the end) -
3 30
(missing the:
) -
10:30pm
(We do not want anything like “pm” or “am” in our input. Only the digits and colon) -
26:00
(first two digits must be <= 23) -
10:61
(last two digits must be <= 59)
You may find the isdigit
function from ctypes.hpp
and ctypes.cpp
useful. You should just be able to call this function in your check-time.cpp
file.
We will require that you make use of your simple_string
for this part of the assignment. You should check how check-time.hpp
includes ctypes.hpp
and do something similar for simple_string.hpp
. You will also need to modify the makefile to test your code. See the compilation section below for how to do this.
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 described above
- Your code should use the
simple_string
you 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 simple_string
“objects”
simple_string str = from_cstring("Hello!");
// prints "Hello!" to the terminal
cout << str << endl;
Just add this code to your simple_string.cpp
file:
std::ostream& operator<<(std::ostream& os, const simple_string to_print) {
os << to_print.data;
return os;
}
Characters and ASCII
You may have learned this in CIT 5930 or another course, but one thing that may help in this assignment is remembering that char
in C and C++ are ASCII characters. Everything comes down to just bits and bytes, so everything is sort of just a number in computer memory, including char
. We can see this if we do something like:
char c = 'H';
int x = (int) c;
cout << x << endl;
// prints 72, which is the numeric ASCII value for the character 'H'
We can take advantadge of this in our code when checking for a valid time string.
For example:
char a = '6';
char b = '3';
bool c = (b <= a); // c gets set to `true`
You may also want to look at isdigit
in ctypes.cpp
for some more inspiration on what is possible :)
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
exit
EXIT_SUCCESS
EXIT_FAILURE
- iostream
cout
cerr
endl
You are also allowed to use the provided files ctypes.hpp
and ctypes.cpp
as well as simple_string
.
Compilation
Header Files and Header Guards
For this assignment we gave you simple_string.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 simple_string
code, they would need to “import” our code by doing #include "simple_string.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 my_file.hpp
.
You can see an example of this in ctypes.hpp
.
For your code to get full credit, you will need to add header guards like this to your simple_string.hpp
.
We highly suggest looking at ctypes.hpp
for help.
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 have varying details in different C++ compiler (or may not be supported by a compiler at all!). Thus we are not going to use #pragma once
in this class)
Makefile
You must also modify the provided Makefile
to compile simple_string.o
and then change check-time
to use your simple_string.o
.
We provide most of the makefile for you, and you will also note that the makefile includes steps for building ctypes.o
and then using it to compile your check-time
program. You should use what we do in the Makefile
with building ctypes.cpp
into ctypes.o
and using it to make check-time
as inspiration for what you need to do for simple_string
. We will also go over makefiles either in the first lecture or the first recitation, so look at those materials if you need to refresh yourself on this material!
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
simple_string.o
fromsimple_string.cpp
andsimple_string.hpp
- modify the
check-time
rule to also usesimple-string.o
- modify the
check-time.o
rule to also usesimple-string.h
- modify the
clean
command also cleans up thesimple-string.o
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 ./check-time 11:59
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 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.
Autograder
This assignment has an autograder to test the functionality of your simple_string
“object” and the behaviour of your check-time
program.
We would normally give you unit tests to check the functionality of your .cpp files, but we didn’t do that this time to keep the Makefile
simpler for you.
When you submit to gradescope (see below) it should run tests for you.
If you want to look at the unit test source, you can look at this file here: test_simple_string.cpp
We HIGHLY suggest testing your code locally, especially check-time
.
Submission
Please submit your completed Makefile
, simple_string.cpp
simple_string.hpp
and check-time.cpp
to Gradescope