Lab #2: Types, Variables, Expressions, and Conditionals¶
Due Date Wednesday, April 6th at 9pm.
The purpose of this lab is to give you practice working with types, variables, expressions, conditionals in C.
Objectives¶
Gain practice with:
arithmetic and logical expressions
conditional statements
conditional expressions
bitwise operators
Getting Started¶
You will be using the same repository for all the labs in this course.
To pick up the files you need for this lab (aka, the distribution),
you should navigate to your cmsc15200/labs-GITHUB-USERID
directory
(where GITHUB_USERID
) is replaced with your GitHUb account name
and then run:
$ git pull upstream main
(We will use $
to indicate the Linux command-line prompt. It is
not part of the command that you will run.)
If this command succeeded, you will have a subdirectory named
lab2
. You will do all of your work for this lab in this
directory. See the file README.md
for a description of the files
in the distribution.
Compiling and testing your code¶
Please read this section carefully
Instead of compiling your code by hand using clang
, you will be
using make
, a tool that automatically builds executables from
source code. make
reads a file, called Makefile
, that
specifies how to construct a target program. We will provide the
necessary makefiles.
For the this lab, the Makefile
has targets for compiling your code
with tests that you write, for compiling your code with our automated
tests, and for cleaning up generated files. To use this file to
compile your code, run one of the following commands from the Linux
command-line:
$ make student_test_lab2
$ make test_lab2
$ make
The first command will compile your code in lab2.c
along with any
tests you have added to student_test_lab2.c
and will, if possible,
generate an executable (student_test_lab2
). To run the resulting
executable, you can run:
$ ./student_test_lab2
We have provided some simple tests as examples in
student_test_lab2.c
. You should add tests of your own as your
work on your code.
The second command will compile your code with our automated tests and
will, if possible, generate an executable (test_lab2
). To run all
of the tests using the resulting executable, run this command from the
command line:
$ ./test_lab2 -f --verbose
The -f
flag tells the test code to stop after the first failure.
The --verbose
flag generates information about which specific tests
passed and failed.
The tests are broken into suites. To run a specific suite or test,
you can add a filter. For example, to run all of the tests in clip
suite, you would run:
$ ./test_lab2 -f --verbose --filter="clip/*"
To run a specific test, replace the *
with the name of the test.
For example, the first test in the clip
suite, you would run:
$ ./test_lab2 -f --verbose --filter="clip/test0"
The code in test_lab2.c
is easily readable. If you fail a test,
look for the corresponding suite and test in test_lab.c
to see the
arguments passed to your function and the expected result. Don’t just
tinker with your code in the hopes of hitting upon a
solution. Instead, try to reason through why your code is failing on
those specific inputs and what it means more generally about potential
flaws in your implementation.
Deliverables¶
Note
IMPORTANT: Some tasks in this lab require you to use a restricted subset of C. For example, Task 2 and Task 3 require you to implement the same function using different C constructs.
You will only get full credit for these questions if your code meets these restrictions.
Please note that you are welcome to define variables and use arithmetic operations as appropriate in all five tasks.
Task 1¶
Complete the function compute_div_fraction
which given two
integers, x
and y
, computes the fractional part of x
divided by y
. For example, compute_div_fraction(3, 2)
would
return 0.5
.
Task 2¶
Complete the function in_range_1
, which takes four parameters:
lb
- the lower bound of a range,ub
- the upper bound of a range,x
- a value,is_inclusive
- a boolean that indicates whether the end points are considered part of the range
The function should return true
if x
is in the range between
lb
and ub
and false otherwise. The end points should be
included in the range only when is_inclusive
is true. For
example, in_range_1(0.0, 1.0, 0.0, false)
should return false,
because the value is the same as the lower bound and is_inclusive
is false.
Restriction: your implementation may use only relational operators
(<
, <=
, >
, >=
, ==
, !=
) and logical operators (that is,
&&
, ||
, or !
) for this task. You may not use
conditional statements or conditional expressions for this task.
You will not get full credit for this task if you do not follow the restrictions.
Task 3¶
Complete the function in_range_2
, which takes four parameters:
lb
- the lower bound of a range,ub
- the upper bound of a range,x
- a value,is_inclusive
- a boolean that indicates whether the end points are considered part of the range
The function should return true
if x
is in the range between
lb
and ub
and false otherwise. The end points should be
included in the range only when is_inclusive
is true.
Restriction: your implementation may use only relational operators
(<
, <=
, >
, >=
, ==
, !=
) and conditional statements.
You may not use logical operators (that is, &&
, ||
, or
!
) for this task.
You will not get full credit for this task if you do not follow the restrictions.
Task 4¶
Complete the function clip
, which takes three doubles x
,
lb
, and ub
and clips the value x
to be in the interval
between lb
and ub
(inclusive). If x
is less than lb
,
it should be clipped to lb
. If x
is greater than ub
, it
should be clipped to ub
. Otherwise, just return x. For example,
clip(2.5, 3.0, 5.0)
should return 3.0
.
Restriction You must complete this task using conditional expressions. You may not use conditional statements. Recall that the format of a conditonal expression is <expr1> ? <expr2> : <expr3>
.
You will not get full credit for this task if you do not follow the restrictions.
Task 5¶
Complete the function extract_flag
, which takes data, represented
as unsigned int
and the index of a choice bit. If the value of
data at the choice bit is zero, then your function should return the
value of the bit at choice_bit - 1
. If the value of the choice
bit is one, the your function should return the value of the bit at
choice_bit - 2
. For example, extract_flag(0x5, 2)
will return
1
, because the value of bit 2 is 1
, which means that the
function should return the value at bit 0. The call
extract_flag(0x127, 7)
should return 0
, because the value at
bit 7 in data is 0
and the value at bit 6 is 0
.
- Restrictions You may only use bitwise operations (
&
,|
, ~
,>>
, and<<
) for this task. You may not use logical operators or conditionals.
You will not get full credit for this task if you do not follow the restrictions.
We encourage you to work through some examples on paper before you start to write code for any task, but it is especially important for this one.
Some useful stuff¶
Most labs in this course will contains some material that is intended to help you become more fluent in Linux. Today’s topics—direction and piping–are an essential part of using Linux.
There are no deliverables for this section. That does not mean you should skip it!
Redirection¶
The examples in this section will use commands that we’ve not yet discussed. Refer to the man pages for information about unfamiliar commands.
As we already know, commands like pwd
, ls
, and cat
will
print output to screen by default. Sometimes, however, we may prefer
to write the output of these commands to a file. In Linux, we can
redirect the output of a program to a file of our choosing. This
operation is done with the >
operator.
Before you start, run:
$ make clean
in your lab2
directory to get rid of the executables you created
as you worked on the tasks.
Try the following example from within your labs2
directory and
compare your output with ours:
$ touch test-0.txt
$ ls > test-1.txt
$ cat test-1.txt
Makefile
README.md
grader.c
lab2.c
lab2.h
my_echo.c
student_test_lab2.c
test-0.txt
test-1.txt
test_lab2.c
$ echo "Hello World" > test-2.txt
$ cat test-2.txt
Hello World
$ cat test-2.txt > test-1.txt; cat test-1.txt
Hello World
You can run two commands sequentially by separating them by a
semicolon (;
) as seen in the last example.
Two important things to note:
If you redirect to a file that does not exist, that file will be created.
If you redirect to a file that already exists, the contents of that file will be overwritten.
You can use the append operator (>>
) to append the output of
command to the end of an existing file rather than overwrite the
contents of that file.
Not only can we redirect the output of a program to a file, we can
also have a program receive its input from a file. This operation is
done with the <
operator. For example:
$ make my_echo
clang -g -Wall -O0 my_echo.c -o my_echo
$ ./my_echo < test-2.txt
$ rm test-*.txt
In general, all Linux processes can perform input/output operations
through, at least, the keyboard and the screen. More specifically,
there are three ‘input/output streams’: standard input (or stdin
),
standard output (or stdout
), and standard error (or stderr
).
The code in my_echo.c
simply reads information from stdin
and
writes it back out to stdout
. The redirection operators change
the bindings of these streams from the keyboard and/or screen to files.
We’ll discuss stderr
later in the term.
Piping¶
In addition to the ability to direct output to and receive input from files, Linux provides a very powerful capability called piping. Piping allows one program to receive as input the output of another program, like so:
$ program1 | program2
In this example, the output of program1 is used as the input of
program2. Or to put it more technically, the stdout
of
program1
is connected to the stdin
of program2
.
As another more concrete example, consider the man
command with
the -k
option that we discussed in Lab #1. Let’s assume that you
hadn’t yet been introduced to the mkdir
command. How would you
look for the command to create a directory? First attempts:
$ man -k "create directory"
create directory: nothing appropriate
$ man -k "directory"
(a bunch of mostly irrelevant output)
As we can see, neither of these options is particularly helpful. However, with
piping, we can combine man -k
with a powerful command line utility called
grep
(see man pages) to find what we need:
$ man -k "directory" | grep "create"
mkdir (2) - create a directory
mkdirat (2) - create a directory
mkdtemp (3) - create a unique temporary directory
mkfontdir (1) - create an index of X font files in a directory
mklost+found (8) - create a lost+found directory on a mounted Linux second extended fil...
mktemp (1) - create a temporary file or directory
pam_mkhomedir (8) - PAM module to create users home directory
update-info-dir (8) - update or create index file from all installed info files in directory
vgmknodes (8) - recreate volume group directory and logical volume special files
Nice.
Exercises¶
Use piping to chain together the
printenv
andtail
commands to display the last 10 lines of output fromprintenv
.Replicate the above functionality without using the
|
operator. (hint: Use redirection and a temporary file.)
Grading¶
For this assignment, the weights will be:
Completeness: 60%
Code Quality: 40%
The code quality score will largely be determined by whether your implementation stays within the allowed constructs for each task.
Preparing your submission¶
There are three things you should do before you submit your work.
First, make sure you have added the required information to the header
comment in lab2.c
:
your name,
the names of anyone beyond the course staff you discussed the
assignment with, and - links to any resources that you used other than course materials and the course textbook.
Please remove the explanation for what is required. Note: write
None
in the relevant field, to indicate that you did not use any
sources and/or consult anyone beyond the course staff.
Second, remove directives, such as:
// YOUR CODE HERE
// Replace 0.0 with an appropriate return value
from your code. Make sure to recompile and rerun the tests after you make these changes. It is easy to introduce a syntax error at this stage.
Third, add, commit, and push your work to GitHub.
And finally, get your directory into a clean state. Run make
clean
to remove any executables that are laying around.
Submission¶
To submit your work, you will need to add, commit, and push your code
to GitHub. Before you upload your code to GitHub, run git status
.
to make sure you did not forget to add/commit any of the required
files. Then upload your submission to Gradescope under the lab2
assignment. Make sure to choose the right assignment on Gradescope!
You are welcome to upload your code to Gradescope multiple times before the deadline, but it is wildly inefficient to use Gradescope as a compute server. You should test your code on the CS Linux machines and only upload it when you think you are finished or when you are getting close to the deadline and want to do a “safety” submission.
You are responsible for making sure that the code you upload compiles and runs. You will get zero credit for code that does not compile.
Finally, a reminder that we will not accept late work.