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:

  1. arithmetic and logical expressions

  2. conditional statements

  3. conditional expressions

  4. 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:

  1. If you redirect to a file that does not exist, that file will be created.

  2. 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

  1. Use piping to chain together the printenv and tail commands to display the last 10 lines of output from printenv.

  2. 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.