Select Page

How to Succeed on this Project
Read the entirety of this specification in detail. It contains a lot of information that will make the project go more smoothly if you take the time to understand it before starting your project.
Know the purpose of each file in the starter code. You will only need to modify two files to complete this project, but you should read this specification to understand why the other files are present. In particular, understand which files you are or are not allowed to modify to avoid autograder issues.
Know how to run your code independently of the Makefile we provide. The results you see from make test will not be enough to tell you what is going wrong with your code. You will need to be able to test out your code manually to figure out precisely where it may be producing undesirable results.
Expect to get stuck, and exercise patience and persistence. Often, the act of writing a sophisticated computer program will take time and critical thinking. Do not expect solutions to bugs to be immediately clear to you. Getting stuck like this is a normal part of the learning process in subjects like mathematics and computer science. We are happy to help in office hours but will not give out answers – we can only point you in the right direction or suggest helpful examples to consider.
Start early. Give yourself time to get stuck, discover insights about what is going wrong, and overcome obstacles. It can be hard to think creatively about solutions to programming issues when you feel the pressure of an imminent deadline.
If you have questions, your best option is to ask in a public Piazza post. This gets your question in front of as many people as quickly as possible and also lets others benefit by seeing the answer to your question. However, do not post code in a public Piazza post–if you have questions specific to a part of your code, send those in a private Piazza post to the instructors, or email it to them, or go to office hours for help.
Familiarize yourself with the late submission policy detailed in the syllabus so you are not caught off-guard. No submissions are accepted more than 48 hours after the deadline. Extensions are generally not granted for projects except in the case of a documented medical emergency.
Introduction

Basic application programming in C is an essential step downward toward the lower levels of computing. This project explores several fundamental aspects of getting work done in C:

Dynamic memory management with malloc() and free()
Reading and writing files in both text and binary formats
Displaying information to the screen
Using data structures with the C struct primitive

You will implement a simple interactive matrix program that allows the user to create, view, and perform various operations on a matrix of integer values. You will also add support for saving and loading matrices to and from both text and binary files.

Grading Criteria

Credit for this assignment will be given based on two categories:

Automated Testing (60%): We have provided several tests along with instructions on how to run these tests from the command line. To pass the tests, you will need to ensure that your code compiles and runs according to the given s. In particular, the output from your program must exactly match the expected output, so make sure you verify that your code works as expected.
Manual Inspection (40%): Graders will be manually reviewing your code to check for certain features – mainly that you have properly implemented the expected solution and that your code adheres to a reasonable structure and style.
Manual Grading Criteria
OVERALL
1 point: Reasonably short functions (nothing over 50 lines is typically needed)
1 point: Expected functionality implemented in correct locations in the program files
3 points: Follows some consistent style and indentation scheme, no ugly regions of code with all closing or opening braces at same indentation level
2 points: Includes appropriate documentation in-line for parts of the code that may be more complex. All TODO comments removed when the referenced task is complete.
MATRIX_FREE
3 points: Properly frees all matrix entries and matrix data structure itself
MATRIX_PUT
1 point: Correct access of matrix data to change value
MATRIX_GET
1 point: Correct access of matrix data to retrieve value
MATRIX_SUM
2 points: Correct access of matrix data and correct logic to sum contents
MATRIX_MAX
2 points: Correct access of matrix data and correct logic to find the max value
MATRIX_READ_TEXT
1 point: Proper function call and arguments for opening text file
1 point: Handles errors when opening file
1 point: Valid logic for reading in matrix dimensions
1 point: Valid logic for reading in matrix contents
1 point: Correctly creates new matrix and initializes its entries
1 point: Closes file when done
MATRIX_WRITE_BIN
1 point: Proper function call and arguments for opening binary file
1 point: Handles errors when opening file
1 point: Valid logic for writing out matrix dimensions
1 point: Valid logic for writing out matrix contents
1 point: Closes file when done
MATRIX_READ_BIN
1 point: Proper function call and arguments for opening binary file
1 point: Handles errors when opening file
1 point: Valid logic for reading in matrix dimensions
1 point: Valid logic for reading in matrix contents
1 point: Correctly creates new matrix and initializes its entries
1 point: Closes file when done
SMOCK_MAIN
2 points: Clear structure to main loop, selecting a command to execute, then executing that command
3 points: All commands correctly handled (calling correct functions, storing and outputting results)
2 points: Clear effort to free matrix at correct points in program
Getting Started

Take the following steps to get started on this project:

Read the rest of this document carefully and thoroughly. It explains what you should do and how your code is expected to behave. Reading this will help you write correct code earlier rather than later, saving you time on debugging.
Download the starter code for this project, linked at the top of this page. Unzip it and examine the provided code. Pay particular attention to the comments we have included. These describe important aspects of the starter code and also include some guidance on how to approach the project.
Get coding. Don’t wait too long before starting as this will only make the project more stressful for you and may result in a late submission.
Ask questions. If it’s not clear how to proceed, post to Piazza or visit an office hours session.
Makefile

A Makefile is provided as part of this project, much like you have seen in the lab assignments. Building programs in C can be tedious, and most people use build systems, of which make is the oldest. The instructions and dependencies to create programs are written in a Makefile which is then interpreted by the make program. This program then invokes gcc and other commands on your behalf.

The Makefile for this project supports the following commands:

make: Compile all code, produce an executable smock program
make clean: Remove all compiled items, useful if you want to recompile everything from scratch
make clean-tests: Removes all files created during execution of the tests
make zip: Create a zip file for submission to Gradescope
make test: Run all test cases
make test testnum=5: Run test case #5 only
make test testnum=4-8: Run test cases #4 through #8 (inclusive) only
make test testnum=”4,8,15″: Run test cases #4, #8, and #15 only. Note the enclosing double quotes.
Automated Tests

Automated tests are included with the project’s starter code. These tests are known to work on lab machines but in most cases they should run identically in x86_64 Linux environments such as the Windows Subsystem for Linux or a virtual machine.

When debugging, you are encouraged to make use of the testnum argument as shown in the makecommand above to run only a specific test case you’re having trouble with, both to limit the amount of output you have to dig through and to reduce the time it takes to produce test results. Running all of the tests every time will be slow and painful.

Manually Running Your Code

The tests can help guide you to a correct solution, but they are not enough for productive debugging. Avoid spamming make test without understanding how your program works and thinking critically about what you are changing in your code, as it will not serve you well in the long run.

Once it is compiled, you can run your code manually from the command line like so:

proj1-code > ./smock
SMOCK – Simple Matrix Operations for C Knowledge
Commands:
new : Create new x matrix
put : Change entry (i,j) of current matrix
get : Retrieve entry (i,j) of current matrix
print: Print out entries in the current matrix
sum: Compute and print out sum of all elements in current matrix
max: Compute and print out maximum of all elements in current matrix
clear: Delete current matrix
write_text : Write current matrix to a text file
read_text : Read a matrix from a text file
write_bin : Write current matrix to a binary file
read_bin : Read current matrix from a binary file
exit: Quit this program
>>

The smock program always starts by printing out a list of available commands (most of which you will have to implement to complete the project), printing out a prompt (>>) to notify the user that they should type in a command, and then reading the user’s input from terminal once it has been entered.

At this point, you are free to type in whatever sequence of commands you like. Testing out a custom scenario where you devise input for your program, predict what you expect your program to do, and then observe how its actual behavior compares to your prediction is an important part of the debugging process.

Starter Code

Download the zip file linked at the top of this page to obtain the starter code for this project. You should see the following files:

File Purpose Notes
Makefile Build Build file to compile code and run test cases
matrix.h Provided Header file for the matrix data structure. Make sure to read over this; there is lots of information in the comments.
matrix.c Edit Implementations of matrix functions. We have given you a starting point, but you will need to fill in the bodies of most functions in this file.
smock_main.c Edit The command-line interface for interacting with a matrix. We have given you a starting point, but you will need to fill in most of this file.
testius Testing Script to run test cases
test_cases Testing Directory containing specification of all test cases, their inputs, and the expected outputs for your program

You are only allowed to modify the matrix.c and smock_main.c files. The Gradescope autograder will use unaltered versions of the remaining files when testing your code for grading purposes, but will also check that you have not altered the other files.

Note that the starter code should successfully compile, but it will not pass most of the automatic tests.

Implementing the Matrix Program
Completing the Matrix Implementation

First, you will need to complete the implementation of the matrix data structure. Start by reading through matrix.h. This file contains the C struct definition for a matrix data structure. A matrix struct contains two unsigned values to track the number of rows and columns in the matrix.

The most interesting aspect of the matrix data structure is the actual matrix contents, stored in a two-dimensional, dynamically allocated array of integers. Each element of this 2D array is another array representing a single row of the matrix. Remember that dynamically allocated arrays created with malloc()are represented as pointers. Therefore, the matrix’s data is represented as a dynamically allocated array of pointers, represented as a pointer to a pointer, or “double pointer.”

To keep things simpler, we will assume matrices are zero-indexed in this project. That is, the first row of a matrix is row 0, the second row of a matrix is row 1, and so on. The first column of a matrix is column 0, the second column of a matrix is column 1, and so on.

This structure is hopefully made more clear by the diagram below. Additionally, we have already provided the necessary code to initialize a new matrix data structure for you in matrix.c. Later on in this course, we will introduce a better way to store matrix data, but this is sufficient for this point in the course.

The matrix.h file also contains declarations for all of the functions that are (or will be) fully defined in matrix.c. You are encouraged to read over the comments in the header file to understand what each function is expected to do.

Once you’re ready, start implementing functions in matrix.c. Two of the necessary operations are already implemented for you, and may serve as examples for you to implement the remaining operations. These two operations are:

matrix_init: Initialize a new matrix. Already provided.
matrix_write_text: Save a matrix to a text file. Already provided.

You then need to implement the following operations:

matrix_free: Free the memory allocated for a matrix
matrix_put: Modify an individual element within a matrix
matrix_get: Retrieve an individual element within a matrix
matrix_sum: Add up all of the entries in a matrix
matrix_max: Compute the maximum value stored in a matrix
matrix_read_text: Load a matrix from a text file.
matrix_write_bin: Save a matrix to a binary file.
matrix_read_bin: Load a matrix from a binary file.
Matrix File Formats

You will be expected to adhere to specific formats for your matrix text and binary files. We will test this by checking how your program behaves when given pre-made text and binary matrix files that we have included in the test_cases directory of the starter code.

Note: We will not expect you do deal with incorrectly formatted files in this project. You can assume that all files used in the automated tests will follow the rules given below, including the files we have provided with the starter code.

TEXT FILES
The first line of a matrix text file should contain two positive integers representing the number of rows and number of columns in the matrix.
Each of the following lines should contain a sequence of integers, representing a single row of the matrix

For example, say we have the following matrix:

[4815162342]

This matrix would be represented in a text file like so:

2 3
4 8 15
16 23 42

Note: To keep your code simple, it is okay to have a trailing space at the end of each line in the text file representing a row of the matrix. It is also okay to have a trailing new line (n) at the end of the file.

BINARY FILES

Remember, binary files do not have “words” or lines” – they are simply a sequence of bytes.

A matrix binary file should start with two unsigned integer values, representing the number of rows and number of columns in the matrix.
Next, the file contains a sequence of integer values representing the entries of the matrix in row-major order.

The example matrix given above would be represented in a binary file with the following structure:


Completing the Interactive User Interface

Your matrix program features an interactive user interface that is similar to the application you will see in Lab 2.

There is one important change for this project, however: the interactive user interface tracks a single activematrix, and the user is allowed to change from one active matrix to another within a single execution of the smock program. When the application is first started, there is no active matrix, so the user is not allowed to perform operations like modifying a matrix entry, computing a matrix sum, or writing a matrix to a file.

The user can either create a new active matrix with the new command (and manually enter the dimensions and content of the matrix) or load in a matrix from a text or binary file with the read_text and read_bincommands, respectively. Once an active matrix is set, the user is free to perform operations against that matrix by typing in commands.

If there is a currently active matrix instance, the user cannot switch to a new matrix without first discarding the current matrix by using the clear command.

Here are the details on each command your application is expected to support:

EXIT (ALREADY PROVIDED)

Exits the application. This is already implemented for you.

NEW (ALREADY PROVIDED)

This is also implemented for you. Creates a new active matrix with the specified number of rows and columns. The user is then expected to type in each entry of the new matrix as well. For example, the following command creates a new matrix with 3 rows 2 columns, and entries containing the integers 1 through 6:

>> new 3 2
1 2
3 4
5 6

Note that the program will keep waiting for more input (it will not display the >> prompt for the next command) until the required number of matrix entries have been typed in by the user.

If there is already an active matrix, then an error message is printed to the screen as follows:

>> new 2 2
Error: You must clear the current matrix first

Note that the error is printed out before the user has to type in any matrix entries, and then the program will loop and ask the user for a new command.

PUT

Modifies the entry at row i and column j of the currently active matrix (remember, rows and columns are zero-indexed in this application). For example, the following command sets the matrix entry at row 1 and column 0 to the value 4:

>> put 1 0 4

Note: You may assume that the values for i and j are always valid and do not exceed the maximum row or column index of the current matrix. That is, you don’t need to worry about the user typing in indexes that are “out of bounds” of the current matrix.

If there is no currently active matrix, the program should print out an error message as follows:

>> put 1 0 4
Error: There is no active matrix

Note that the application still needs to read in the provided input values for i, j, and val in this error situation, because they are part of the command, but then the application will end up ignoring this input.

GET

Retrieves and prints out the entry at row i and column j of the currently active matrix. For example, assuming that the currently active matrix has the value 3061 stored in row 7, column 8, we would see the following output:

>> get 7 8
3061

Note: You may assume that the values for i and j are always valid and do not exceed the maximum row or column index of the current matrix. That is, you don’t need to worry about the user typing in indexes that are “out of bounds” of the current matrix.

If there is no currently active matrix, the program should print out an error message as follows:

>> get 7 8
Error: There is no active matrix

Note that the application still needs to read in the provided input values for i and j in this error situation, because they are part of the command, but then the application will end up ignoring this input.

PRINT

Prints out all entries in the currently active matrix. Each row of the matrix should be on its own line. For example, say we have an active matrix with the following contents:

[100010001]

Then we would see the following behavior for the print command:

>> print
1 0 0
0 1 0
0 0 1

Note: To keep things simple, it is okay if you have a trailing space at the end of each line representing a matrix row. This will be ignored by the test scripts.

If there is no currently active matrix, then an error message should be shown on the screen as follows:

>> print
Error: There is no active matrix
SUM

Adds up all of the elements stored in the currently active matrix and prints out the result. For example, say we have an active matrix with the following contents:

[4815162342]

Then we would expect the following behavior from our program:

>> sum
108

If there is no currently active matrix, then the program prints out an error message as follows:

>> sum
Error: There is no active matrix
MAX

Computes the maximum element stored in the currently active matrix and prints out the result. For example, if we have the same matrix as shown above for the sum command, we would expect the following behavior from our program:

>> max
42

If there is no currently active matrix, then the program prints out an error message as follows:

>> max
Error: There is no active matrix
CLEAR

Discards the currently active matrix (meaning there will be no active matrix after the completion of this command) and frees all memory associated with the currently active matrix.

If there is no active matrix, an error message is shown on the screen as follows:

>> clear
Error: There is no active matrix
WRITE_TEXT

Writes a text representation of the currently active matrix (format detailed above) to the file file_name. Upon success, a message is printed to the screen as follows:

>> write_text test.txt
Matrix successfully written to text file

If writing to the file fails, an error message is printed to the screen as follows:

>> write_text test.txt
Failed to write matrix to text file

If there is no currently active matrix, an error message is printed to the screen as follows:

>> write_text test.txt
Error: There is no active matrix

Note that the application still needs to read in the provided input value for file_name in this error situation, because it is part of the command, but then the application will end up ignoring this input.

READ_TEXT

Reads in a text representation of a matrix from the file file_name. Upon success, a message is printed to the screen as follows:

>> read_text test.txt
Matrix successfully read from text file

Note: You may assume that any text file you are asked to read is in the correct format as detailed above. Of course, this assumption does depend on a correct implementation of matrix_write_text in matrix.c.

If reading from the file fails (e.g., the file does not exist), an error message is printed to the screen as follows:

>> read_text does_not_exist.txt
Failed to read matrix from text file

If there is already an active matrix, the user cannot read in a new matrix from a text file until they clear the current matrix. In this situation, an error message should be printed to the screen like so:

>> read_text test.txt
Error: You must clear the current matrix first

Note that the application still needs to read in the provided input value for file_name in this error situation, because it is part of the command, but then the application will end up ignoring this input.

WRITE_BIN

Writes a binary representation of the currently active matrix (format detailed above) to the file file_name. Upon success, a message is printed to the screen as follows:

>> write_bin test.bin
Matrix successfully written to binary file

If writing to the file fails, an error message is printed to the screen as follows:

>> write_bin test.bin
Failed to write matrix to binary file

If there is no currently active matrix, an error message is printed to the screen as follows:

>> write_bin test.bin
Error: There is no active matrix

Note that the application still needs to read in the provided input value for file_name in this error situation, because it is part of the command, but then the application will end up ignoring this input.

READ_BIN

Reads in a binary representation of a matrix from the file file_name. Upon success, a message is printed to the screen as follows:

>> read_bin test.bin
Matrix successfully read from binary file

Note: You may assume that any binary file you are asked to read is in the correct format as detailed above. Of course, this assumption does depend on a correct implementation of matrix_write_bin in matrix.c.

If reading from the file fails (e.g., the file does not exist), an error message is printed to the screen as follows:

>> read_bin does_not_exist.bin
Failed to read matrix from binary file

If there is already an active matrix, the user cannot read in a new matrix from a binary file until they clear the current matrix. In this situation, an error message should be printed to the screen like so:

>> read_bin test.bin
Error: You must clear the current matrix first

Note that the application still needs to read in the provided input value for file_name in this error situation, because it is part of the command, but then the application will end up ignoring this input.

Putting it All Together

The automated tests will also check how your program behaves when several commands are run in sequence. Below is an example of a user session with smock that shows how it should behave when dealing with several commands in sequence.

Let’s say a user creates a new matrix, modifies a few of its original entries, then performs a few matrix operations and writes the matrix to a text file. They then try to create a new matrix by mistake before clearing out the current matrix. Finally, the user retrieves a few individual elements, clears the matrix, and reloads it from the text file they had just created. Here is what their session should look like:

$ ./smock
SMOCK – Simple Matrix Operations for C Knowledge
Commands:
new : Create new x matrix
put : Change entry (i,j) of current matrix
get : Retrieve entry (i,j) of current matrix
print: Print out entries in the current matrix
sum: Compute and print out sum of all elements in current matrix
max: Compute and print out maximum of all elements in current matrix
clear: Delete current matrix
write_text : Write current matrix to a text file
read_text : Read a matrix from a text file
write_bin : Write current matrix to a binary file
read_bin : Read current matrix from a binary file
exit: Quit this program
>> new 3 3
1 0 1
0 1 0
0 0 1
>> print
1 0 1
0 1 0
0 0 1
>> put 1 0 3
>> print
1 0 1
3 1 0
0 0 1
>> put 2 1 4
>> print
1 0 1
3 1 0
0 4 1
>> sum
11
>> max
4
>> write_text my_matrix.txt
Matrix successfully written to text file
>> new 2 2
Error: You must clear the current matrix first
>> get 0 0
1
>> get 0 1
0
>> get 2 2
1
>> get 2 1
4
>> clear
>> print
Error: There is no active matrix
>> read_text my_matrix.txt
Matrix successfully read from text file
>> print
1 0 1
3 1 0
0 4 1
>> exit
Debugging Advice
Start by studying the starter code (including the comments in matrix.h) and this specification carefully. You can’t pass the tests if you don’t understand the expectations for your implementation and therefore what we are testing for.
When gcc produces warnings and error messages, it’s usually best to start at the top of its output and work your way down. Often there are a few messages from gcc that stem from the same initial problem upstream.
Debug systematically, not by guessing. Be sure you know how to run the smock program yourself, then run it and enter commands yourself to gain an understanding of which events are causing which problems in your code.
Try implementing one command at a time and testing it before moving on, rather than trying to implement all the functions, then complete smock_main, and then test the entire solution all at once.
Use valgrind: Spend time familiarizing yourself with this tool and the output it produces. It can catch a variety of problems in your code such as where a segmentation fault is coming from, where you have forgotten to free memory, or where you read or write against invalid memory addresses.
Study the test cases. They contain all of the input we will provide to your program and all of the output we expect from your program. They often involve a specific sequence of steps to check that you are handling both normal situations and error cases (like trying to read from a non-existent matrix file) properly.
Project Submission

You must submit your code to Gradescope to receive credit for this project. The zip file your submit must adhere to certain expectations, and to make that easy for you to create, we have provided a method to generate that file. Run the make zip command to create a zip archive suitable for uploading to Gradescope.

Your zip file should be submitted to the Project 1 assignment listed on our course’s Gradescope page. See Lab 1 if you need further guidance on submitting your code and viewing the autograder’s test results. Make sure that you review the autograder’s test results, in case they differ from the results you saw when doing your own testing. Remember that the Gradescope autograder results are the final results, regardless of what your own testing shows.